|
@@ -17,6 +17,7 @@
|
|
|
#define CDNS_PCIE_EP_MIN_APERTURE 128 /* 128 bytes */
|
|
#define CDNS_PCIE_EP_MIN_APERTURE 128 /* 128 bytes */
|
|
|
#define CDNS_PCIE_EP_IRQ_PCI_ADDR_NONE 0x1
|
|
#define CDNS_PCIE_EP_IRQ_PCI_ADDR_NONE 0x1
|
|
|
#define CDNS_PCIE_EP_IRQ_PCI_ADDR_LEGACY 0x3
|
|
#define CDNS_PCIE_EP_IRQ_PCI_ADDR_LEGACY 0x3
|
|
|
|
|
+#define CDNS_PCIE_EP_MSIX_BAR 0x5
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* struct cdns_pcie_ep - private data for this PCIe endpoint controller driver
|
|
* struct cdns_pcie_ep - private data for this PCIe endpoint controller driver
|
|
@@ -336,6 +337,64 @@ static int cdns_pcie_ep_get_msi(struct pci_epc *epc, u8 fn, u8 vfn)
|
|
|
return mme;
|
|
return mme;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+static int cdns_pcie_ep_get_msix(struct pci_epc *epc, u8 func_no, u8 vfunc_no)
|
|
|
|
|
+{
|
|
|
|
|
+ u32 sriov_cap = CDNS_PCIE_EP_FUNC_SRIOV_CAP_OFFSET;
|
|
|
|
|
+ struct cdns_pcie_ep *ep = epc_get_drvdata(epc);
|
|
|
|
|
+ struct cdns_pcie *pcie = &ep->pcie;
|
|
|
|
|
+ u32 cap = CDNS_PCIE_EP_FUNC_MSIX_CAP_OFFSET;
|
|
|
|
|
+ u32 first_vf_offset, stride;
|
|
|
|
|
+ u32 val, reg;
|
|
|
|
|
+
|
|
|
|
|
+ if (vfunc_no > 0) {
|
|
|
|
|
+ first_vf_offset = cdns_pcie_ep_fn_readw(pcie, func_no, sriov_cap
|
|
|
|
|
+ + PCI_SRIOV_VF_OFFSET);
|
|
|
|
|
+ stride = cdns_pcie_ep_fn_readw(pcie, func_no, sriov_cap +
|
|
|
|
|
+ PCI_SRIOV_VF_STRIDE);
|
|
|
|
|
+ func_no = func_no + first_vf_offset + ((vfunc_no - 1) * stride);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ reg = cap + PCI_MSIX_FLAGS;
|
|
|
|
|
+ val = cdns_pcie_ep_fn_readw(pcie, func_no, reg);
|
|
|
|
|
+ if (!(val & PCI_MSIX_FLAGS_ENABLE))
|
|
|
|
|
+ return -EINVAL;
|
|
|
|
|
+
|
|
|
|
|
+ val &= PCI_MSIX_FLAGS_QSIZE;
|
|
|
|
|
+
|
|
|
|
|
+ return val;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static int cdns_pcie_ep_set_msix(struct pci_epc *epc, u8 fn, u8 vfn,
|
|
|
|
|
+ u16 interrupts)
|
|
|
|
|
+{
|
|
|
|
|
+ u32 sriov_cap = CDNS_PCIE_EP_FUNC_SRIOV_CAP_OFFSET;
|
|
|
|
|
+ struct cdns_pcie_ep *ep = epc_get_drvdata(epc);
|
|
|
|
|
+ struct cdns_pcie *pcie = &ep->pcie;
|
|
|
|
|
+ u32 cap = CDNS_PCIE_EP_FUNC_MSIX_CAP_OFFSET;
|
|
|
|
|
+ u32 first_vf_offset, stride;
|
|
|
|
|
+ u32 val, reg;
|
|
|
|
|
+
|
|
|
|
|
+ if (vfn > 0) {
|
|
|
|
|
+ first_vf_offset = cdns_pcie_ep_fn_readw(pcie, fn, sriov_cap +
|
|
|
|
|
+ PCI_SRIOV_VF_OFFSET);
|
|
|
|
|
+ stride = cdns_pcie_ep_fn_readw(pcie, fn, sriov_cap +
|
|
|
|
|
+ PCI_SRIOV_VF_STRIDE);
|
|
|
|
|
+ fn = fn + first_vf_offset + ((vfn - 1) * stride);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ reg = cap + PCI_MSIX_FLAGS;
|
|
|
|
|
+ val = cdns_pcie_ep_fn_readw(pcie, fn, reg);
|
|
|
|
|
+ val &= ~PCI_MSIX_FLAGS_QSIZE;
|
|
|
|
|
+ val |= interrupts;
|
|
|
|
|
+ cdns_pcie_ep_fn_writew(pcie, fn, reg, val);
|
|
|
|
|
+ /* Set MSIX BAR and offset */
|
|
|
|
|
+ cdns_pcie_ep_fn_writel(pcie, fn, 0xb4, CDNS_PCIE_EP_MSIX_BAR);
|
|
|
|
|
+ /* Set PBA BAR and offset. BAR must match MSIX BAR */
|
|
|
|
|
+ cdns_pcie_ep_fn_writel(pcie, fn, 0xb8, 0x10000 | CDNS_PCIE_EP_MSIX_BAR);
|
|
|
|
|
+
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
static void cdns_pcie_ep_assert_intx(struct cdns_pcie_ep *ep, u8 fn, u8 vfn,
|
|
static void cdns_pcie_ep_assert_intx(struct cdns_pcie_ep *ep, u8 fn, u8 vfn,
|
|
|
u8 intx, bool is_asserted)
|
|
u8 intx, bool is_asserted)
|
|
|
{
|
|
{
|
|
@@ -458,6 +517,77 @@ static int cdns_pcie_ep_send_msi_irq(struct cdns_pcie_ep *ep, u8 fn, u8 vfn,
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+static int cdns_pcie_ep_send_msix_irq(struct cdns_pcie_ep *ep, u8 fn, u8 vfn,
|
|
|
|
|
+ u16 interrupt_num)
|
|
|
|
|
+{
|
|
|
|
|
+ u32 sriov_cap = CDNS_PCIE_EP_FUNC_SRIOV_CAP_OFFSET;
|
|
|
|
|
+ struct cdns_pcie *pcie = &ep->pcie;
|
|
|
|
|
+ u32 cap = CDNS_PCIE_EP_FUNC_MSIX_CAP_OFFSET;
|
|
|
|
|
+ u16 flags;
|
|
|
|
|
+ u64 pci_addr_mask = 0xff;
|
|
|
|
|
+ u16 tbl_offset = 0;
|
|
|
|
|
+ u32 bar_addr_upper, bar_addr_lower;
|
|
|
|
|
+ u32 msg_addr_upper, msg_addr_lower;
|
|
|
|
|
+ u32 msg_data;
|
|
|
|
|
+ u64 tbl_addr, msg_addr;
|
|
|
|
|
+ void __iomem *msix_tbl;
|
|
|
|
|
+
|
|
|
|
|
+ u32 first_vf_offset, stride;
|
|
|
|
|
+
|
|
|
|
|
+ if (vfn > 0) {
|
|
|
|
|
+ first_vf_offset = cdns_pcie_ep_fn_readw(pcie, fn, sriov_cap +
|
|
|
|
|
+ PCI_SRIOV_VF_OFFSET);
|
|
|
|
|
+ stride = cdns_pcie_ep_fn_readw(pcie, fn, sriov_cap +
|
|
|
|
|
+ PCI_SRIOV_VF_STRIDE);
|
|
|
|
|
+ fn = fn + first_vf_offset + ((vfn - 1) * stride);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* Check whether the MSI-X feature has been enabled by the PCI host. */
|
|
|
|
|
+ flags = cdns_pcie_ep_fn_readw(pcie, fn, cap + PCI_MSIX_FLAGS);
|
|
|
|
|
+ if (!(flags & PCI_MSIX_FLAGS_ENABLE))
|
|
|
|
|
+ return -EINVAL;
|
|
|
|
|
+ /* We want local address, not address on host. Table is at offset 0 */
|
|
|
|
|
+ bar_addr_lower = cdns_pcie_readl(pcie, CDNS_PCIE_AT_IB_EP_FUNC_BAR_ADDR0
|
|
|
|
|
+ (fn, CDNS_PCIE_EP_MSIX_BAR));
|
|
|
|
|
+ bar_addr_upper = cdns_pcie_readl(pcie, CDNS_PCIE_AT_IB_EP_FUNC_BAR_ADDR1
|
|
|
|
|
+ (fn, CDNS_PCIE_EP_MSIX_BAR));
|
|
|
|
|
+
|
|
|
|
|
+ tbl_addr = ((u64)bar_addr_upper) << 32 | bar_addr_lower;
|
|
|
|
|
+ tbl_addr += (tbl_offset + ((interrupt_num - 1) * PCI_MSIX_ENTRY_SIZE));
|
|
|
|
|
+ tbl_addr &= PCI_BASE_ADDRESS_MEM_MASK;
|
|
|
|
|
+ msix_tbl = phys_to_virt(tbl_addr);
|
|
|
|
|
+ if (!msix_tbl)
|
|
|
|
|
+ return -EINVAL;
|
|
|
|
|
+
|
|
|
|
|
+ msg_addr_lower = readl(msix_tbl + PCI_MSIX_ENTRY_LOWER_ADDR);
|
|
|
|
|
+ msg_addr_upper = readl(msix_tbl + PCI_MSIX_ENTRY_UPPER_ADDR);
|
|
|
|
|
+ msg_addr = ((u64)msg_addr_upper) << 32 | msg_addr_lower;
|
|
|
|
|
+
|
|
|
|
|
+ msg_data = readl(msix_tbl + PCI_MSIX_ENTRY_VECTOR_CTRL);
|
|
|
|
|
+ if (msg_data & 0x1)
|
|
|
|
|
+ return -EINVAL;
|
|
|
|
|
+
|
|
|
|
|
+ msg_data = readl(msix_tbl + PCI_MSIX_ENTRY_DATA);
|
|
|
|
|
+
|
|
|
|
|
+ iounmap(msix_tbl);
|
|
|
|
|
+
|
|
|
|
|
+ /* Set the outbound region if needed. */
|
|
|
|
|
+ if (unlikely(ep->irq_pci_addr != (msg_addr & ~pci_addr_mask) ||
|
|
|
|
|
+ ep->irq_pci_fn != fn)) {
|
|
|
|
|
+ /* First region was reserved for IRQ writes. */
|
|
|
|
|
+ cdns_pcie_set_outbound_region(pcie, fn, 0,
|
|
|
|
|
+ false,
|
|
|
|
|
+ ep->irq_phys_addr,
|
|
|
|
|
+ msg_addr & ~pci_addr_mask,
|
|
|
|
|
+ pci_addr_mask + 1);
|
|
|
|
|
+ ep->irq_pci_addr = (msg_addr & ~pci_addr_mask);
|
|
|
|
|
+ ep->irq_pci_fn = fn;
|
|
|
|
|
+ }
|
|
|
|
|
+ writel(msg_data, ep->irq_cpu_addr + (msg_addr & pci_addr_mask));
|
|
|
|
|
+
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
static int cdns_pcie_ep_raise_irq(struct pci_epc *epc, u8 fn, u8 vfn,
|
|
static int cdns_pcie_ep_raise_irq(struct pci_epc *epc, u8 fn, u8 vfn,
|
|
|
enum pci_epc_irq_type type,
|
|
enum pci_epc_irq_type type,
|
|
|
u16 interrupt_num)
|
|
u16 interrupt_num)
|
|
@@ -477,6 +607,9 @@ static int cdns_pcie_ep_raise_irq(struct pci_epc *epc, u8 fn, u8 vfn,
|
|
|
case PCI_EPC_IRQ_MSI:
|
|
case PCI_EPC_IRQ_MSI:
|
|
|
return cdns_pcie_ep_send_msi_irq(ep, fn, vfn, interrupt_num);
|
|
return cdns_pcie_ep_send_msi_irq(ep, fn, vfn, interrupt_num);
|
|
|
|
|
|
|
|
|
|
+ case PCI_EPC_IRQ_MSIX:
|
|
|
|
|
+ return cdns_pcie_ep_send_msix_irq(ep, fn, vfn, interrupt_num);
|
|
|
|
|
+
|
|
|
default:
|
|
default:
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
@@ -511,7 +644,7 @@ static int cdns_pcie_ep_start(struct pci_epc *epc)
|
|
|
static const struct pci_epc_features cdns_pcie_epc_features = {
|
|
static const struct pci_epc_features cdns_pcie_epc_features = {
|
|
|
.linkup_notifier = false,
|
|
.linkup_notifier = false,
|
|
|
.msi_capable = true,
|
|
.msi_capable = true,
|
|
|
- .msix_capable = false,
|
|
|
|
|
|
|
+ .msix_capable = true,
|
|
|
.align = 256,
|
|
.align = 256,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -542,6 +675,8 @@ static const struct pci_epc_ops cdns_pcie_epc_ops = {
|
|
|
.unmap_addr = cdns_pcie_ep_unmap_addr,
|
|
.unmap_addr = cdns_pcie_ep_unmap_addr,
|
|
|
.set_msi = cdns_pcie_ep_set_msi,
|
|
.set_msi = cdns_pcie_ep_set_msi,
|
|
|
.get_msi = cdns_pcie_ep_get_msi,
|
|
.get_msi = cdns_pcie_ep_get_msi,
|
|
|
|
|
+ .set_msix = cdns_pcie_ep_set_msix,
|
|
|
|
|
+ .get_msix = cdns_pcie_ep_get_msix,
|
|
|
.raise_irq = cdns_pcie_ep_raise_irq,
|
|
.raise_irq = cdns_pcie_ep_raise_irq,
|
|
|
.start = cdns_pcie_ep_start,
|
|
.start = cdns_pcie_ep_start,
|
|
|
.get_features = cdns_pcie_ep_get_features,
|
|
.get_features = cdns_pcie_ep_get_features,
|