diff options
Diffstat (limited to 'drivers/pci/host/pci-layerscape-ep.c')
-rw-r--r-- | drivers/pci/host/pci-layerscape-ep.c | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/drivers/pci/host/pci-layerscape-ep.c b/drivers/pci/host/pci-layerscape-ep.c new file mode 100644 index 0000000..7b99982 --- /dev/null +++ b/drivers/pci/host/pci-layerscape-ep.c @@ -0,0 +1,309 @@ +/* + * PCIe Endpoint driver for Freescale Layerscape SoCs + * + * Copyright 2015 Freescale Semiconductor, Inc. + * + * Author: Minghuan Lian <Minghuan.Lian@freescale.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 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of_pci.h> +#include <linux/of_platform.h> +#include <linux/of_irq.h> +#include <linux/of_address.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/resource.h> +#include <linux/debugfs.h> +#include <linux/time.h> +#include <linux/uaccess.h> + +#include "pci-layerscape-ep.h" + +struct ls_ep_dev * +ls_pci_ep_find(struct ls_pcie *pcie, int dev_id) +{ + struct ls_ep_dev *ep; + + list_for_each_entry(ep, &pcie->ep_list, node) { + if (ep->dev_id == dev_id) + return ep; + } + + return NULL; +} + +static void ls_pcie_try_cfg2(struct ls_pcie *pcie, int pf, int vf) +{ + if (pcie->sriov) + writel(PCIE_LCTRL0_VAL(pf, vf), + pcie->dbi + PCIE_LUT_BASE + PCIE_LUT_LCTRL0); +} + +static bool ls_pcie_is_bridge(struct ls_pcie *pcie) +{ + u32 header_type = 0; + + header_type = readl(pcie->dbi + (PCI_HEADER_TYPE & ~0x3)); + header_type = (header_type >> 16) & 0x7f; + + return header_type == PCI_HEADER_TYPE_BRIDGE; +} + +void ls_pcie_iatu_outbound_set(struct ls_pcie *pcie, int idx, int type, + u64 cpu_addr, u64 pci_addr, u32 size) +{ + writel(PCIE_ATU_REGION_OUTBOUND | idx, + pcie->dbi + PCIE_ATU_VIEWPORT); + writel(lower_32_bits(cpu_addr), + pcie->dbi + PCIE_ATU_LOWER_BASE); + writel(upper_32_bits(cpu_addr), + pcie->dbi + PCIE_ATU_UPPER_BASE); + writel(lower_32_bits(cpu_addr + size - 1), + pcie->dbi + PCIE_ATU_LIMIT); + writel(lower_32_bits(pci_addr), + pcie->dbi + PCIE_ATU_LOWER_TARGET); + writel(upper_32_bits(pci_addr), + pcie->dbi + PCIE_ATU_UPPER_TARGET); + writel(type, pcie->dbi + PCIE_ATU_CR1); + writel(PCIE_ATU_ENABLE, pcie->dbi + PCIE_ATU_CR2); +} + +/* Use bar match mode and MEM type as default */ +void ls_pcie_iatu_inbound_set(struct ls_pcie *pcie, int idx, + int bar, u64 phys) +{ + writel(PCIE_ATU_REGION_INBOUND | idx, pcie->dbi + PCIE_ATU_VIEWPORT); + writel((u32)phys, pcie->dbi + PCIE_ATU_LOWER_TARGET); + writel(phys >> 32, pcie->dbi + PCIE_ATU_UPPER_TARGET); + writel(PCIE_ATU_TYPE_MEM, pcie->dbi + PCIE_ATU_CR1); + writel(PCIE_ATU_ENABLE | PCIE_ATU_BAR_MODE_ENABLE | + PCIE_ATU_BAR_NUM(bar), pcie->dbi + PCIE_ATU_CR2); +} + +void ls_pcie_ep_dev_cfg_enable(struct ls_ep_dev *ep) +{ + ls_pcie_try_cfg2(ep->pcie, ep->pf_idx, ep->vf_idx); +} + +void ls_pcie_ep_setup_bar(void *bar_base, int bar, u32 size) +{ + if (size < 4 * 1024) + return; + + switch (bar) { + case 0: + writel(size - 1, bar_base + PCI_BASE_ADDRESS_0); + break; + case 1: + writel(size - 1, bar_base + PCI_BASE_ADDRESS_1); + break; + case 2: + writel(size - 1, bar_base + PCI_BASE_ADDRESS_2); + writel(0, bar_base + PCI_BASE_ADDRESS_3); + break; + case 4: + writel(size - 1, bar_base + PCI_BASE_ADDRESS_4); + writel(0, bar_base + PCI_BASE_ADDRESS_5); + break; + default: + break; + } +} + +void ls_pcie_ep_dev_setup_bar(struct ls_ep_dev *ep, int bar, u32 size) +{ + struct ls_pcie *pcie = ep->pcie; + void *bar_base; + + if (size < 4 * 1024) + return; + + if (pcie->sriov) + bar_base = pcie->dbi; + else + bar_base = pcie->dbi + PCIE_NO_SRIOV_BAR_BASE; + + ls_pcie_ep_dev_cfg_enable(ep); + ls_pcie_ep_setup_bar(bar_base, bar, size); +} + +static int ls_pcie_ep_dev_init(struct ls_pcie *pcie, int pf_idx, int vf_idx) +{ + struct ls_ep_dev *ep; + + ep = devm_kzalloc(pcie->dev, sizeof(*ep), GFP_KERNEL); + if (!ep) + return -ENOMEM; + + ep->pcie = pcie; + ep->pf_idx = pf_idx; + ep->vf_idx = vf_idx; + if (vf_idx) + ep->dev_id = pf_idx + 4 + 4 * (vf_idx - 1); + else + ep->dev_id = pf_idx; + + if (ep->vf_idx) + dev_set_name(&ep->dev, "pf%d-vf%d", + ep->pf_idx, + ep->vf_idx); + else + dev_set_name(&ep->dev, "pf%d", + ep->pf_idx); + + list_add_tail(&ep->node, &pcie->ep_list); + + return 0; +} + +static int ls_pcie_ep_init(struct ls_pcie *pcie) +{ + u32 sriov_header; + int pf, vf, i, j; + + sriov_header = readl(pcie->dbi + PCIE_SRIOV_POS); + + if (PCI_EXT_CAP_ID(sriov_header) == PCI_EXT_CAP_ID_SRIOV) { + pcie->sriov = PCIE_SRIOV_POS; + pf = PCIE_PF_NUM; + vf = PCIE_VF_NUM; + } else { + pcie->sriov = 0; + pf = 1; + vf = 0; + } + + for (i = 0; i < pf; i++) { + for (j = 0; j <= vf; j++) + ls_pcie_ep_dev_init(pcie, i, j); + } + + return 0; +} + +static struct ls_pcie_ep_drvdata ls1043_drvdata = { + .lut_offset = 0x10000, + .ltssm_shift = 24, + .lut_dbg = 0x7fc, +}; + +static struct ls_pcie_ep_drvdata ls1046_drvdata = { + .lut_offset = 0x80000, + .ltssm_shift = 24, + .lut_dbg = 0x407fc, +}; + +static struct ls_pcie_ep_drvdata ls2080_drvdata = { + .lut_offset = 0x80000, + .ltssm_shift = 0, + .lut_dbg = 0x7fc, +}; + +static const struct of_device_id ls_pcie_ep_of_match[] = { + { .compatible = "fsl,ls1021a-pcie", }, + { .compatible = "fsl,ls1043a-pcie", .data = &ls1043_drvdata }, + { .compatible = "fsl,ls1046a-pcie", .data = &ls1046_drvdata }, + { .compatible = "fsl,ls2080a-pcie", .data = &ls2080_drvdata }, + { .compatible = "fsl,ls2085a-pcie", .data = &ls2080_drvdata }, + { }, +}; +MODULE_DEVICE_TABLE(of, ls_pcie_ep_of_match); + +static int ls_pcie_ep_probe(struct platform_device *pdev) +{ + struct ls_pcie *pcie; + struct resource *dbi_base, *cfg_res; + const struct of_device_id *match; + int ret; + + match = of_match_device(ls_pcie_ep_of_match, &pdev->dev); + if (!match) + return -ENODEV; + + pcie = devm_kzalloc(&pdev->dev, sizeof(*pcie), GFP_KERNEL); + if (!pcie) + return -ENOMEM; + + pcie->dev = &pdev->dev; + INIT_LIST_HEAD(&pcie->ep_list); + + dbi_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs"); + pcie->dbi = devm_ioremap_resource(&pdev->dev, dbi_base); + if (IS_ERR(pcie->dbi)) { + dev_err(&pdev->dev, "missing *regs* space\n"); + return PTR_ERR(pcie->dbi); + } + + pcie->drvdata = match->data; + pcie->lut = pcie->dbi + pcie->drvdata->lut_offset; + + if (ls_pcie_is_bridge(pcie)) + return -ENODEV; + + dev_info(pcie->dev, "in EP mode\n"); + + cfg_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "config"); + if (cfg_res) + pcie->out_base = cfg_res->start; + else { + dev_err(&pdev->dev, "missing *config* space\n"); + return -ENODEV; + } + + ret = ls_pcie_ep_init(pcie); + if (ret) + return ret; + + ls_pcie_ep_dbgfs_init(pcie); + + platform_set_drvdata(pdev, pcie); + + return 0; +} + +static int ls_pcie_ep_dev_remove(struct ls_ep_dev *ep) +{ + list_del(&ep->node); + + return 0; +} + +static int ls_pcie_ep_remove(struct platform_device *pdev) +{ + struct ls_pcie *pcie = platform_get_drvdata(pdev); + struct ls_ep_dev *ep, *tmp; + + if (!pcie) + return 0; + + ls_pcie_ep_dbgfs_remove(pcie); + + list_for_each_entry_safe(ep, tmp, &pcie->ep_list, node) + ls_pcie_ep_dev_remove(ep); + + return 0; +} + +static struct platform_driver ls_pcie_ep_driver = { + .driver = { + .name = "ls-pcie-ep", + .owner = THIS_MODULE, + .of_match_table = ls_pcie_ep_of_match, + }, + .probe = ls_pcie_ep_probe, + .remove = ls_pcie_ep_remove, +}; + +module_platform_driver(ls_pcie_ep_driver); + +MODULE_AUTHOR("Minghuan Lian <Minghuan.Lian@freescale.com>"); +MODULE_DESCRIPTION("Freescale Layerscape PCIe EP driver"); +MODULE_LICENSE("GPL v2"); |