PCI: cadence: Add MSI-X support to Endpoint driver
Implement ->set_msix() and ->get_msix() callback functions in order to configure MSIX capability in the PCIe endpoint controller. Add cdns_pcie_ep_send_msix_irq() to send MSIX interrupts to Host. cdns_pcie_ep_send_msix_irq() gets the MSIX table address (virtual address) from "struct cdns_pcie_epf" that gets initialized in ->set_bar() call back function. [kishon@ti.com: Re-implement MSIX support in accordance with the re-designed core MSI-X interfaces] Link: https://lore.kernel.org/r/20200722110317.4744-11-kishon@ti.com Signed-off-by: Alan Douglas <adouglas@cadence.com> Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
This commit is contained in:
		
							parent
							
								
									e3bca37d15
								
							
						
					
					
						commit
						3ef5d16f50
					
				| @ -51,6 +51,7 @@ static int cdns_pcie_ep_set_bar(struct pci_epc *epc, u8 fn, | ||||
| 				struct pci_epf_bar *epf_bar) | ||||
| { | ||||
| 	struct cdns_pcie_ep *ep = epc_get_drvdata(epc); | ||||
| 	struct cdns_pcie_epf *epf = &ep->epf[fn]; | ||||
| 	struct cdns_pcie *pcie = &ep->pcie; | ||||
| 	dma_addr_t bar_phys = epf_bar->phys_addr; | ||||
| 	enum pci_barno bar = epf_bar->barno; | ||||
| @ -111,6 +112,8 @@ static int cdns_pcie_ep_set_bar(struct pci_epc *epc, u8 fn, | ||||
| 		CDNS_PCIE_LM_EP_FUNC_BAR_CFG_BAR_CTRL(b, ctrl)); | ||||
| 	cdns_pcie_writel(pcie, reg, cfg); | ||||
| 
 | ||||
| 	epf->epf_bar[bar] = epf_bar; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| @ -118,6 +121,7 @@ static void cdns_pcie_ep_clear_bar(struct pci_epc *epc, u8 fn, | ||||
| 				   struct pci_epf_bar *epf_bar) | ||||
| { | ||||
| 	struct cdns_pcie_ep *ep = epc_get_drvdata(epc); | ||||
| 	struct cdns_pcie_epf *epf = &ep->epf[fn]; | ||||
| 	struct cdns_pcie *pcie = &ep->pcie; | ||||
| 	enum pci_barno bar = epf_bar->barno; | ||||
| 	u32 reg, cfg, b, ctrl; | ||||
| @ -139,6 +143,8 @@ static void cdns_pcie_ep_clear_bar(struct pci_epc *epc, u8 fn, | ||||
| 
 | ||||
| 	cdns_pcie_writel(pcie, CDNS_PCIE_AT_IB_EP_FUNC_BAR_ADDR0(fn, bar), 0); | ||||
| 	cdns_pcie_writel(pcie, CDNS_PCIE_AT_IB_EP_FUNC_BAR_ADDR1(fn, bar), 0); | ||||
| 
 | ||||
| 	epf->epf_bar[bar] = NULL; | ||||
| } | ||||
| 
 | ||||
| static int cdns_pcie_ep_map_addr(struct pci_epc *epc, u8 fn, phys_addr_t addr, | ||||
| @ -224,6 +230,50 @@ static int cdns_pcie_ep_get_msi(struct pci_epc *epc, u8 fn) | ||||
| 	return mme; | ||||
| } | ||||
| 
 | ||||
| static int cdns_pcie_ep_get_msix(struct pci_epc *epc, u8 func_no) | ||||
| { | ||||
| 	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 val, reg; | ||||
| 
 | ||||
| 	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, u16 interrupts, | ||||
| 				 enum pci_barno bir, u32 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 val, reg; | ||||
| 
 | ||||
| 	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 */ | ||||
| 	reg = cap + PCI_MSIX_TABLE; | ||||
| 	val = offset | bir; | ||||
| 	cdns_pcie_ep_fn_writel(pcie, fn, reg, val); | ||||
| 
 | ||||
| 	/* Set PBA BAR and offset.  BAR must match MSIX BAR */ | ||||
| 	reg = cap + PCI_MSIX_PBA; | ||||
| 	val = (offset + (interrupts * PCI_MSIX_ENTRY_SIZE)) | bir; | ||||
| 	cdns_pcie_ep_fn_writel(pcie, fn, reg, val); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void cdns_pcie_ep_assert_intx(struct cdns_pcie_ep *ep, u8 fn, | ||||
| 				     u8 intx, bool is_asserted) | ||||
| { | ||||
| @ -333,6 +383,51 @@ static int cdns_pcie_ep_send_msi_irq(struct cdns_pcie_ep *ep, u8 fn, | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int cdns_pcie_ep_send_msix_irq(struct cdns_pcie_ep *ep, u8 fn, | ||||
| 				      u16 interrupt_num) | ||||
| { | ||||
| 	u32 cap = CDNS_PCIE_EP_FUNC_MSIX_CAP_OFFSET; | ||||
| 	u32 tbl_offset, msg_data, reg; | ||||
| 	struct cdns_pcie *pcie = &ep->pcie; | ||||
| 	struct pci_epf_msix_tbl *msix_tbl; | ||||
| 	struct cdns_pcie_epf *epf; | ||||
| 	u64 pci_addr_mask = 0xff; | ||||
| 	u64 msg_addr; | ||||
| 	u16 flags; | ||||
| 	u8 bir; | ||||
| 
 | ||||
| 	/* 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; | ||||
| 
 | ||||
| 	reg = cap + PCI_MSIX_TABLE; | ||||
| 	tbl_offset = cdns_pcie_ep_fn_readl(pcie, fn, reg); | ||||
| 	bir = tbl_offset & PCI_MSIX_TABLE_BIR; | ||||
| 	tbl_offset &= PCI_MSIX_TABLE_OFFSET; | ||||
| 
 | ||||
| 	epf = &ep->epf[fn]; | ||||
| 	msix_tbl = epf->epf_bar[bir]->addr + tbl_offset; | ||||
| 	msg_addr = msix_tbl[(interrupt_num - 1)].msg_addr; | ||||
| 	msg_data = msix_tbl[(interrupt_num - 1)].msg_data; | ||||
| 
 | ||||
| 	/* Set the outbound region if needed. */ | ||||
| 	if (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, | ||||
| 				  enum pci_epc_irq_type type, | ||||
| 				  u16 interrupt_num) | ||||
| @ -346,6 +441,9 @@ static int cdns_pcie_ep_raise_irq(struct pci_epc *epc, u8 fn, | ||||
| 	case PCI_EPC_IRQ_MSI: | ||||
| 		return cdns_pcie_ep_send_msi_irq(ep, fn, interrupt_num); | ||||
| 
 | ||||
| 	case PCI_EPC_IRQ_MSIX: | ||||
| 		return cdns_pcie_ep_send_msix_irq(ep, fn, interrupt_num); | ||||
| 
 | ||||
| 	default: | ||||
| 		break; | ||||
| 	} | ||||
| @ -383,7 +481,7 @@ static int cdns_pcie_ep_start(struct pci_epc *epc) | ||||
| static const struct pci_epc_features cdns_pcie_epc_features = { | ||||
| 	.linkup_notifier = false, | ||||
| 	.msi_capable = true, | ||||
| 	.msix_capable = false, | ||||
| 	.msix_capable = true, | ||||
| }; | ||||
| 
 | ||||
| static const struct pci_epc_features* | ||||
| @ -400,6 +498,8 @@ static const struct pci_epc_ops cdns_pcie_epc_ops = { | ||||
| 	.unmap_addr	= cdns_pcie_ep_unmap_addr, | ||||
| 	.set_msi	= cdns_pcie_ep_set_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, | ||||
| 	.start		= cdns_pcie_ep_start, | ||||
| 	.get_features	= cdns_pcie_ep_get_features, | ||||
| @ -458,6 +558,11 @@ int cdns_pcie_ep_setup(struct cdns_pcie_ep *ep) | ||||
| 	if (of_property_read_u8(np, "max-functions", &epc->max_functions) < 0) | ||||
| 		epc->max_functions = 1; | ||||
| 
 | ||||
| 	ep->epf = devm_kcalloc(dev, epc->max_functions, sizeof(*ep->epf), | ||||
| 			       GFP_KERNEL); | ||||
| 	if (!ep->epf) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	ret = pci_epc_mem_init(epc, pcie->mem_res->start, | ||||
| 			       resource_size(pcie->mem_res), PAGE_SIZE); | ||||
| 	if (ret < 0) { | ||||
|  | ||||
| @ -113,6 +113,7 @@ | ||||
| #define CDNS_PCIE_EP_FUNC_BASE(fn)	(((fn) << 12) & GENMASK(19, 12)) | ||||
| 
 | ||||
| #define CDNS_PCIE_EP_FUNC_MSI_CAP_OFFSET	0x90 | ||||
| #define CDNS_PCIE_EP_FUNC_MSIX_CAP_OFFSET	0xb0 | ||||
| 
 | ||||
| /*
 | ||||
|  * Root Port Registers (PCI configuration space for the root port function) | ||||
| @ -302,6 +303,14 @@ struct cdns_pcie_rc { | ||||
| 	bool			avail_ib_bar[CDNS_PCIE_RP_MAX_IB]; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct cdns_pcie_epf - Structure to hold info about endpoint function | ||||
|  * @epf_bar: reference to the pci_epf_bar for the six Base Address Registers | ||||
|  */ | ||||
| struct cdns_pcie_epf { | ||||
| 	struct pci_epf_bar *epf_bar[PCI_STD_NUM_BARS]; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct cdns_pcie_ep - private data for this PCIe endpoint controller driver | ||||
|  * @pcie: Cadence PCIe controller | ||||
| @ -321,6 +330,7 @@ struct cdns_pcie_rc { | ||||
|  * @lock: spin lock to disable interrupts while modifying PCIe controller | ||||
|  *        registers fields (RMW) accessible by both remote RC and EP to | ||||
|  *        minimize time between read and write | ||||
|  * @epf: Structure to hold info about endpoint function | ||||
|  */ | ||||
| struct cdns_pcie_ep { | ||||
| 	struct cdns_pcie	pcie; | ||||
| @ -334,6 +344,7 @@ struct cdns_pcie_ep { | ||||
| 	u8			irq_pending; | ||||
| 	/* protect writing to PCI_STATUS while raising legacy interrupts */ | ||||
| 	spinlock_t		lock; | ||||
| 	struct cdns_pcie_epf	*epf; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user