| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * PCIe endpoint driver for Renesas R-Car SoCs |
| * Copyright (c) 2020 Renesas Electronics Europe GmbH |
| * |
| * Author: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com> |
| */ |
| |
| #include <linux/delay.h> |
| #include <linux/of_address.h> |
| #include <linux/of_platform.h> |
| #include <linux/pci.h> |
| #include <linux/pci-epc.h> |
| #include <linux/platform_device.h> |
| #include <linux/pm_runtime.h> |
| |
| #include "pcie-rcar.h" |
| |
| #define RCAR_EPC_MAX_FUNCTIONS 1 |
| |
| /* Structure representing the PCIe interface */ |
| struct rcar_pcie_endpoint { |
| struct rcar_pcie pcie; |
| phys_addr_t *ob_mapped_addr; |
| struct pci_epc_mem_window *ob_window; |
| u8 max_functions; |
| unsigned int bar_to_atu[MAX_NR_INBOUND_MAPS]; |
| unsigned long *ib_window_map; |
| u32 num_ib_windows; |
| u32 num_ob_windows; |
| }; |
| |
| static void rcar_pcie_ep_hw_init(struct rcar_pcie *pcie) |
| { |
| u32 val; |
| |
| rcar_pci_write_reg(pcie, 0, PCIETCTLR); |
| |
| /* Set endpoint mode */ |
| rcar_pci_write_reg(pcie, 0, PCIEMSR); |
| |
| /* Initialize default capabilities. */ |
| rcar_rmw32(pcie, REXPCAP(0), 0xff, PCI_CAP_ID_EXP); |
| rcar_rmw32(pcie, REXPCAP(PCI_EXP_FLAGS), |
| PCI_EXP_FLAGS_TYPE, PCI_EXP_TYPE_ENDPOINT << 4); |
| rcar_rmw32(pcie, RCONF(PCI_HEADER_TYPE), 0x7f, |
| PCI_HEADER_TYPE_NORMAL); |
| |
| /* Write out the physical slot number = 0 */ |
| rcar_rmw32(pcie, REXPCAP(PCI_EXP_SLTCAP), PCI_EXP_SLTCAP_PSN, 0); |
| |
| val = rcar_pci_read_reg(pcie, EXPCAP(1)); |
| /* device supports fixed 128 bytes MPSS */ |
| val &= ~GENMASK(2, 0); |
| rcar_pci_write_reg(pcie, val, EXPCAP(1)); |
| |
| val = rcar_pci_read_reg(pcie, EXPCAP(2)); |
| /* read requests size 128 bytes */ |
| val &= ~GENMASK(14, 12); |
| /* payload size 128 bytes */ |
| val &= ~GENMASK(7, 5); |
| rcar_pci_write_reg(pcie, val, EXPCAP(2)); |
| |
| /* Set target link speed to 5.0 GT/s */ |
| rcar_rmw32(pcie, EXPCAP(12), PCI_EXP_LNKSTA_CLS, |
| PCI_EXP_LNKSTA_CLS_5_0GB); |
| |
| /* Set the completion timer timeout to the maximum 50ms. */ |
| rcar_rmw32(pcie, TLCTLR + 1, 0x3f, 50); |
| |
| /* Terminate list of capabilities (Next Capability Offset=0) */ |
| rcar_rmw32(pcie, RVCCAP(0), 0xfff00000, 0); |
| |
| /* flush modifications */ |
| wmb(); |
| } |
| |
| static int rcar_pcie_ep_get_window(struct rcar_pcie_endpoint *ep, |
| phys_addr_t addr) |
| { |
| int i; |
| |
| for (i = 0; i < ep->num_ob_windows; i++) |
| if (ep->ob_window[i].phys_base == addr) |
| return i; |
| |
| return -EINVAL; |
| } |
| |
| static int rcar_pcie_parse_outbound_ranges(struct rcar_pcie_endpoint *ep, |
| struct platform_device *pdev) |
| { |
| struct rcar_pcie *pcie = &ep->pcie; |
| char outbound_name[10]; |
| struct resource *res; |
| unsigned int i = 0; |
| |
| ep->num_ob_windows = 0; |
| for (i = 0; i < RCAR_PCI_MAX_RESOURCES; i++) { |
| sprintf(outbound_name, "memory%u", i); |
| res = platform_get_resource_byname(pdev, |
| IORESOURCE_MEM, |
| outbound_name); |
| if (!res) { |
| dev_err(pcie->dev, "missing outbound window %u\n", i); |
| return -EINVAL; |
| } |
| if (!devm_request_mem_region(&pdev->dev, res->start, |
| resource_size(res), |
| outbound_name)) { |
| dev_err(pcie->dev, "Cannot request memory region %s.\n", |
| outbound_name); |
| return -EIO; |
| } |
| |
| ep->ob_window[i].phys_base = res->start; |
| ep->ob_window[i].size = resource_size(res); |
| /* controller doesn't support multiple allocation |
| * from same window, so set page_size to window size |
| */ |
| ep->ob_window[i].page_size = resource_size(res); |
| } |
| ep->num_ob_windows = i; |
| |
| return 0; |
| } |
| |
| static int rcar_pcie_ep_get_pdata(struct rcar_pcie_endpoint *ep, |
| struct platform_device *pdev) |
| { |
| struct rcar_pcie *pcie = &ep->pcie; |
| struct pci_epc_mem_window *window; |
| struct device *dev = pcie->dev; |
| struct resource res; |
| int err; |
| |
| err = of_address_to_resource(dev->of_node, 0, &res); |
| if (err) |
| return err; |
| pcie->base = devm_ioremap_resource(dev, &res); |
| if (IS_ERR(pcie->base)) |
| return PTR_ERR(pcie->base); |
| |
| ep->ob_window = devm_kcalloc(dev, RCAR_PCI_MAX_RESOURCES, |
| sizeof(*window), GFP_KERNEL); |
| if (!ep->ob_window) |
| return -ENOMEM; |
| |
| rcar_pcie_parse_outbound_ranges(ep, pdev); |
| |
| err = of_property_read_u8(dev->of_node, "max-functions", |
| &ep->max_functions); |
| if (err < 0 || ep->max_functions > RCAR_EPC_MAX_FUNCTIONS) |
| ep->max_functions = RCAR_EPC_MAX_FUNCTIONS; |
| |
| return 0; |
| } |
| |
| static int rcar_pcie_ep_write_header(struct pci_epc *epc, u8 fn, u8 vfn, |
| struct pci_epf_header *hdr) |
| { |
| struct rcar_pcie_endpoint *ep = epc_get_drvdata(epc); |
| struct rcar_pcie *pcie = &ep->pcie; |
| u32 val; |
| |
| if (!fn) |
| val = hdr->vendorid; |
| else |
| val = rcar_pci_read_reg(pcie, IDSETR0); |
| val |= hdr->deviceid << 16; |
| rcar_pci_write_reg(pcie, val, IDSETR0); |
| |
| val = hdr->revid; |
| val |= hdr->progif_code << 8; |
| val |= hdr->subclass_code << 16; |
| val |= hdr->baseclass_code << 24; |
| rcar_pci_write_reg(pcie, val, IDSETR1); |
| |
| if (!fn) |
| val = hdr->subsys_vendor_id; |
| else |
| val = rcar_pci_read_reg(pcie, SUBIDSETR); |
| val |= hdr->subsys_id << 16; |
| rcar_pci_write_reg(pcie, val, SUBIDSETR); |
| |
| if (hdr->interrupt_pin > PCI_INTERRUPT_INTA) |
| return -EINVAL; |
| val = rcar_pci_read_reg(pcie, PCICONF(15)); |
| val |= (hdr->interrupt_pin << 8); |
| rcar_pci_write_reg(pcie, val, PCICONF(15)); |
| |
| return 0; |
| } |
| |
| static int rcar_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no, |
| struct pci_epf_bar *epf_bar) |
| { |
| int flags = epf_bar->flags | LAR_ENABLE | LAM_64BIT; |
| struct rcar_pcie_endpoint *ep = epc_get_drvdata(epc); |
| u64 size = 1ULL << fls64(epf_bar->size - 1); |
| dma_addr_t cpu_addr = epf_bar->phys_addr; |
| enum pci_barno bar = epf_bar->barno; |
| struct rcar_pcie *pcie = &ep->pcie; |
| u32 mask; |
| int idx; |
| int err; |
| |
| idx = find_first_zero_bit(ep->ib_window_map, ep->num_ib_windows); |
| if (idx >= ep->num_ib_windows) { |
| dev_err(pcie->dev, "no free inbound window\n"); |
| return -EINVAL; |
| } |
| |
| if ((flags & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_IO) |
| flags |= IO_SPACE; |
| |
| ep->bar_to_atu[bar] = idx; |
| /* use 64-bit BARs */ |
| set_bit(idx, ep->ib_window_map); |
| set_bit(idx + 1, ep->ib_window_map); |
| |
| if (cpu_addr > 0) { |
| unsigned long nr_zeros = __ffs64(cpu_addr); |
| u64 alignment = 1ULL << nr_zeros; |
| |
| size = min(size, alignment); |
| } |
| |
| size = min(size, 1ULL << 32); |
| |
| mask = roundup_pow_of_two(size) - 1; |
| mask &= ~0xf; |
| |
| rcar_pcie_set_inbound(pcie, cpu_addr, |
| 0x0, mask | flags, idx, false); |
| |
| err = rcar_pcie_wait_for_phyrdy(pcie); |
| if (err) { |
| dev_err(pcie->dev, "phy not ready\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static void rcar_pcie_ep_clear_bar(struct pci_epc *epc, u8 fn, u8 vfn, |
| struct pci_epf_bar *epf_bar) |
| { |
| struct rcar_pcie_endpoint *ep = epc_get_drvdata(epc); |
| enum pci_barno bar = epf_bar->barno; |
| u32 atu_index = ep->bar_to_atu[bar]; |
| |
| rcar_pcie_set_inbound(&ep->pcie, 0x0, 0x0, 0x0, bar, false); |
| |
| clear_bit(atu_index, ep->ib_window_map); |
| clear_bit(atu_index + 1, ep->ib_window_map); |
| } |
| |
| static int rcar_pcie_ep_set_msi(struct pci_epc *epc, u8 fn, u8 vfn, |
| u8 interrupts) |
| { |
| struct rcar_pcie_endpoint *ep = epc_get_drvdata(epc); |
| struct rcar_pcie *pcie = &ep->pcie; |
| u32 flags; |
| |
| flags = rcar_pci_read_reg(pcie, MSICAP(fn)); |
| flags |= interrupts << MSICAP0_MMESCAP_OFFSET; |
| rcar_pci_write_reg(pcie, flags, MSICAP(fn)); |
| |
| return 0; |
| } |
| |
| static int rcar_pcie_ep_get_msi(struct pci_epc *epc, u8 fn, u8 vfn) |
| { |
| struct rcar_pcie_endpoint *ep = epc_get_drvdata(epc); |
| struct rcar_pcie *pcie = &ep->pcie; |
| u32 flags; |
| |
| flags = rcar_pci_read_reg(pcie, MSICAP(fn)); |
| if (!(flags & MSICAP0_MSIE)) |
| return -EINVAL; |
| |
| return ((flags & MSICAP0_MMESE_MASK) >> MSICAP0_MMESE_OFFSET); |
| } |
| |
| static int rcar_pcie_ep_map_addr(struct pci_epc *epc, u8 fn, u8 vfn, |
| phys_addr_t addr, u64 pci_addr, size_t size) |
| { |
| struct rcar_pcie_endpoint *ep = epc_get_drvdata(epc); |
| struct rcar_pcie *pcie = &ep->pcie; |
| struct resource_entry win; |
| struct resource res; |
| int window; |
| int err; |
| |
| /* check if we have a link. */ |
| err = rcar_pcie_wait_for_dl(pcie); |
| if (err) { |
| dev_err(pcie->dev, "link not up\n"); |
| return err; |
| } |
| |
| window = rcar_pcie_ep_get_window(ep, addr); |
| if (window < 0) { |
| dev_err(pcie->dev, "failed to get corresponding window\n"); |
| return -EINVAL; |
| } |
| |
| memset(&win, 0x0, sizeof(win)); |
| memset(&res, 0x0, sizeof(res)); |
| res.start = pci_addr; |
| res.end = pci_addr + size - 1; |
| res.flags = IORESOURCE_MEM; |
| win.res = &res; |
| |
| rcar_pcie_set_outbound(pcie, window, &win); |
| |
| ep->ob_mapped_addr[window] = addr; |
| |
| return 0; |
| } |
| |
| static void rcar_pcie_ep_unmap_addr(struct pci_epc *epc, u8 fn, u8 vfn, |
| phys_addr_t addr) |
| { |
| struct rcar_pcie_endpoint *ep = epc_get_drvdata(epc); |
| struct resource_entry win; |
| struct resource res; |
| int idx; |
| |
| for (idx = 0; idx < ep->num_ob_windows; idx++) |
| if (ep->ob_mapped_addr[idx] == addr) |
| break; |
| |
| if (idx >= ep->num_ob_windows) |
| return; |
| |
| memset(&win, 0x0, sizeof(win)); |
| memset(&res, 0x0, sizeof(res)); |
| win.res = &res; |
| rcar_pcie_set_outbound(&ep->pcie, idx, &win); |
| |
| ep->ob_mapped_addr[idx] = 0; |
| } |
| |
| static int rcar_pcie_ep_assert_intx(struct rcar_pcie_endpoint *ep, |
| u8 fn, u8 intx) |
| { |
| struct rcar_pcie *pcie = &ep->pcie; |
| u32 val; |
| |
| val = rcar_pci_read_reg(pcie, PCIEMSITXR); |
| if ((val & PCI_MSI_FLAGS_ENABLE)) { |
| dev_err(pcie->dev, "MSI is enabled, cannot assert INTx\n"); |
| return -EINVAL; |
| } |
| |
| val = rcar_pci_read_reg(pcie, PCICONF(1)); |
| if ((val & INTDIS)) { |
| dev_err(pcie->dev, "INTx message transmission is disabled\n"); |
| return -EINVAL; |
| } |
| |
| val = rcar_pci_read_reg(pcie, PCIEINTXR); |
| if ((val & ASTINTX)) { |
| dev_err(pcie->dev, "INTx is already asserted\n"); |
| return -EINVAL; |
| } |
| |
| val |= ASTINTX; |
| rcar_pci_write_reg(pcie, val, PCIEINTXR); |
| usleep_range(1000, 1001); |
| val = rcar_pci_read_reg(pcie, PCIEINTXR); |
| val &= ~ASTINTX; |
| rcar_pci_write_reg(pcie, val, PCIEINTXR); |
| |
| return 0; |
| } |
| |
| static int rcar_pcie_ep_assert_msi(struct rcar_pcie *pcie, |
| u8 fn, u8 interrupt_num) |
| { |
| u16 msi_count; |
| u32 val; |
| |
| /* Check MSI enable bit */ |
| val = rcar_pci_read_reg(pcie, MSICAP(fn)); |
| if (!(val & MSICAP0_MSIE)) |
| return -EINVAL; |
| |
| /* Get MSI numbers from MME */ |
| msi_count = ((val & MSICAP0_MMESE_MASK) >> MSICAP0_MMESE_OFFSET); |
| msi_count = 1 << msi_count; |
| |
| if (!interrupt_num || interrupt_num > msi_count) |
| return -EINVAL; |
| |
| val = rcar_pci_read_reg(pcie, PCIEMSITXR); |
| rcar_pci_write_reg(pcie, val | (interrupt_num - 1), PCIEMSITXR); |
| |
| return 0; |
| } |
| |
| static int rcar_pcie_ep_raise_irq(struct pci_epc *epc, u8 fn, u8 vfn, |
| enum pci_epc_irq_type type, |
| u16 interrupt_num) |
| { |
| struct rcar_pcie_endpoint *ep = epc_get_drvdata(epc); |
| |
| switch (type) { |
| case PCI_EPC_IRQ_LEGACY: |
| return rcar_pcie_ep_assert_intx(ep, fn, 0); |
| |
| case PCI_EPC_IRQ_MSI: |
| return rcar_pcie_ep_assert_msi(&ep->pcie, fn, interrupt_num); |
| |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int rcar_pcie_ep_start(struct pci_epc *epc) |
| { |
| struct rcar_pcie_endpoint *ep = epc_get_drvdata(epc); |
| |
| rcar_pci_write_reg(&ep->pcie, MACCTLR_INIT_VAL, MACCTLR); |
| rcar_pci_write_reg(&ep->pcie, CFINIT, PCIETCTLR); |
| |
| return 0; |
| } |
| |
| static void rcar_pcie_ep_stop(struct pci_epc *epc) |
| { |
| struct rcar_pcie_endpoint *ep = epc_get_drvdata(epc); |
| |
| rcar_pci_write_reg(&ep->pcie, 0, PCIETCTLR); |
| } |
| |
| static const struct pci_epc_features rcar_pcie_epc_features = { |
| .linkup_notifier = false, |
| .msi_capable = true, |
| .msix_capable = false, |
| /* use 64-bit BARs so mark BAR[1,3,5] as reserved */ |
| .reserved_bar = 1 << BAR_1 | 1 << BAR_3 | 1 << BAR_5, |
| .bar_fixed_64bit = 1 << BAR_0 | 1 << BAR_2 | 1 << BAR_4, |
| .bar_fixed_size[0] = 128, |
| .bar_fixed_size[2] = 256, |
| .bar_fixed_size[4] = 256, |
| }; |
| |
| static const struct pci_epc_features* |
| rcar_pcie_ep_get_features(struct pci_epc *epc, u8 func_no, u8 vfunc_no) |
| { |
| return &rcar_pcie_epc_features; |
| } |
| |
| static const struct pci_epc_ops rcar_pcie_epc_ops = { |
| .write_header = rcar_pcie_ep_write_header, |
| .set_bar = rcar_pcie_ep_set_bar, |
| .clear_bar = rcar_pcie_ep_clear_bar, |
| .set_msi = rcar_pcie_ep_set_msi, |
| .get_msi = rcar_pcie_ep_get_msi, |
| .map_addr = rcar_pcie_ep_map_addr, |
| .unmap_addr = rcar_pcie_ep_unmap_addr, |
| .raise_irq = rcar_pcie_ep_raise_irq, |
| .start = rcar_pcie_ep_start, |
| .stop = rcar_pcie_ep_stop, |
| .get_features = rcar_pcie_ep_get_features, |
| }; |
| |
| static const struct of_device_id rcar_pcie_ep_of_match[] = { |
| { .compatible = "renesas,r8a774c0-pcie-ep", }, |
| { .compatible = "renesas,rcar-gen3-pcie-ep" }, |
| { }, |
| }; |
| |
| static int rcar_pcie_ep_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct rcar_pcie_endpoint *ep; |
| struct rcar_pcie *pcie; |
| struct pci_epc *epc; |
| int err; |
| |
| ep = devm_kzalloc(dev, sizeof(*ep), GFP_KERNEL); |
| if (!ep) |
| return -ENOMEM; |
| |
| pcie = &ep->pcie; |
| pcie->dev = dev; |
| |
| pm_runtime_enable(dev); |
| err = pm_runtime_resume_and_get(dev); |
| if (err < 0) { |
| dev_err(dev, "pm_runtime_resume_and_get failed\n"); |
| goto err_pm_disable; |
| } |
| |
| err = rcar_pcie_ep_get_pdata(ep, pdev); |
| if (err < 0) { |
| dev_err(dev, "failed to request resources: %d\n", err); |
| goto err_pm_put; |
| } |
| |
| ep->num_ib_windows = MAX_NR_INBOUND_MAPS; |
| ep->ib_window_map = |
| devm_kcalloc(dev, BITS_TO_LONGS(ep->num_ib_windows), |
| sizeof(long), GFP_KERNEL); |
| if (!ep->ib_window_map) { |
| err = -ENOMEM; |
| dev_err(dev, "failed to allocate memory for inbound map\n"); |
| goto err_pm_put; |
| } |
| |
| ep->ob_mapped_addr = devm_kcalloc(dev, ep->num_ob_windows, |
| sizeof(*ep->ob_mapped_addr), |
| GFP_KERNEL); |
| if (!ep->ob_mapped_addr) { |
| err = -ENOMEM; |
| dev_err(dev, "failed to allocate memory for outbound memory pointers\n"); |
| goto err_pm_put; |
| } |
| |
| epc = devm_pci_epc_create(dev, &rcar_pcie_epc_ops); |
| if (IS_ERR(epc)) { |
| dev_err(dev, "failed to create epc device\n"); |
| err = PTR_ERR(epc); |
| goto err_pm_put; |
| } |
| |
| epc->max_functions = ep->max_functions; |
| epc_set_drvdata(epc, ep); |
| |
| rcar_pcie_ep_hw_init(pcie); |
| |
| err = pci_epc_multi_mem_init(epc, ep->ob_window, ep->num_ob_windows); |
| if (err < 0) { |
| dev_err(dev, "failed to initialize the epc memory space\n"); |
| goto err_pm_put; |
| } |
| |
| return 0; |
| |
| err_pm_put: |
| pm_runtime_put(dev); |
| |
| err_pm_disable: |
| pm_runtime_disable(dev); |
| |
| return err; |
| } |
| |
| static struct platform_driver rcar_pcie_ep_driver = { |
| .driver = { |
| .name = "rcar-pcie-ep", |
| .of_match_table = rcar_pcie_ep_of_match, |
| .suppress_bind_attrs = true, |
| }, |
| .probe = rcar_pcie_ep_probe, |
| }; |
| builtin_platform_driver(rcar_pcie_ep_driver); |