diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2017-02-23 11:53:22 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-02-23 11:53:22 -0800 |
commit | 60e8d3e11645a1b9c4197d9786df3894332c1685 (patch) | |
tree | 00f2d71dfc8c9e6cf9fc1033eca3f169249a838e /drivers/pci/dwc/pcie-hisi.c | |
parent | Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net (diff) | |
parent | Merge branch 'pci/virtualization' into next (diff) | |
download | linux-dev-60e8d3e11645a1b9c4197d9786df3894332c1685.tar.xz linux-dev-60e8d3e11645a1b9c4197d9786df3894332c1685.zip |
Merge tag 'pci-v4.11-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/helgaas/pci
Pull PCI updates from Bjorn Helgaas:
- add ASPM L1 substate support
- enable PCIe Extended Tags when supported
- configure PCIe MPS settings on iProc, Versatile, X-Gene, and Xilinx
- increase VPD access timeout
- add ACS quirks for Intel Union Point, Qualcomm QDF2400 and QDF2432
- use new pci_irq_alloc_vectors() in more drivers
- fix MSI affinity memory leak
- remove unused MSI interfaces and update documentation
- remove unused AER .link_reset() callback
- avoid pci_lock / p->pi_lock deadlock seen with perf
- serialize sysfs enable/disable num_vfs operations
- move DesignWare IP from drivers/pci/host/ to drivers/pci/dwc/ and
refactor so we can support both hosts and endpoints
- add DT ECAM-like support for HiSilicon Hip06/Hip07 controllers
- add Rockchip system power management support
- add Thunder-X cn81xx and cn83xx support
- add Exynos 5440 PCIe PHY support
* tag 'pci-v4.11-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/helgaas/pci: (93 commits)
PCI: dwc: Remove dependency of designware on CONFIG_PCI
PCI: dwc: Add CONFIG_PCIE_DW_HOST to enable PCI dwc host
PCI: dwc: Split pcie-designware.c into host and core files
PCI: dwc: designware: Fix style errors in pcie-designware.c
PCI: dwc: designware: Parse "num-lanes" property in dw_pcie_setup_rc()
PCI: dwc: all: Split struct pcie_port into host-only and core structures
PCI: dwc: designware: Get device pointer at the start of dw_pcie_host_init()
PCI: dwc: all: Rename cfg_read/cfg_write to read/write
PCI: dwc: all: Use platform_set_drvdata() to save private data
PCI: dwc: designware: Move register defines to designware header file
PCI: dwc: Use PTR_ERR_OR_ZERO to simplify code
PCI: dra7xx: Group PHY API invocations
PCI: dra7xx: Enable MSI and legacy interrupts simultaneously
PCI: dra7xx: Add support to force RC to work in GEN1 mode
PCI: dra7xx: Simplify probe code with devm_gpiod_get_optional()
PCI: Move DesignWare IP support to new drivers/pci/dwc/ directory
PCI: exynos: Support the PHY generic framework
Documentation: binding: Modify the exynos5440 PCIe binding
phy: phy-exynos-pcie: Add support for Exynos PCIe PHY
Documentation: samsung-phy: Add exynos-pcie-phy binding
...
Diffstat (limited to 'drivers/pci/dwc/pcie-hisi.c')
-rw-r--r-- | drivers/pci/dwc/pcie-hisi.c | 397 |
1 files changed, 397 insertions, 0 deletions
diff --git a/drivers/pci/dwc/pcie-hisi.c b/drivers/pci/dwc/pcie-hisi.c new file mode 100644 index 000000000000..e3e4fedd9f68 --- /dev/null +++ b/drivers/pci/dwc/pcie-hisi.c @@ -0,0 +1,397 @@ +/* + * PCIe host controller driver for HiSilicon SoCs + * + * Copyright (C) 2015 HiSilicon Co., Ltd. http://www.hisilicon.com + * + * Authors: Zhou Wang <wangzhou1@hisilicon.com> + * Dacai Zhu <zhudacai@hisilicon.com> + * Gabriele Paoloni <gabriele.paoloni@huawei.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/interrupt.h> +#include <linux/init.h> +#include <linux/mfd/syscon.h> +#include <linux/of_address.h> +#include <linux/of_pci.h> +#include <linux/platform_device.h> +#include <linux/of_device.h> +#include <linux/pci.h> +#include <linux/pci-acpi.h> +#include <linux/pci-ecam.h> +#include <linux/regmap.h> +#include "../pci.h" + +#if defined(CONFIG_PCI_HISI) || (defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS)) + +static int hisi_pcie_rd_conf(struct pci_bus *bus, u32 devfn, int where, + int size, u32 *val) +{ + struct pci_config_window *cfg = bus->sysdata; + int dev = PCI_SLOT(devfn); + + if (bus->number == cfg->busr.start) { + /* access only one slot on each root port */ + if (dev > 0) + return PCIBIOS_DEVICE_NOT_FOUND; + else + return pci_generic_config_read32(bus, devfn, where, + size, val); + } + + return pci_generic_config_read(bus, devfn, where, size, val); +} + +static int hisi_pcie_wr_conf(struct pci_bus *bus, u32 devfn, + int where, int size, u32 val) +{ + struct pci_config_window *cfg = bus->sysdata; + int dev = PCI_SLOT(devfn); + + if (bus->number == cfg->busr.start) { + /* access only one slot on each root port */ + if (dev > 0) + return PCIBIOS_DEVICE_NOT_FOUND; + else + return pci_generic_config_write32(bus, devfn, where, + size, val); + } + + return pci_generic_config_write(bus, devfn, where, size, val); +} + +static void __iomem *hisi_pcie_map_bus(struct pci_bus *bus, unsigned int devfn, + int where) +{ + struct pci_config_window *cfg = bus->sysdata; + void __iomem *reg_base = cfg->priv; + + if (bus->number == cfg->busr.start) + return reg_base + where; + else + return pci_ecam_map_bus(bus, devfn, where); +} + +#if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS) + +static int hisi_pcie_init(struct pci_config_window *cfg) +{ + struct device *dev = cfg->parent; + struct acpi_device *adev = to_acpi_device(dev); + struct acpi_pci_root *root = acpi_driver_data(adev); + struct resource *res; + void __iomem *reg_base; + int ret; + + /* + * Retrieve RC base and size from a HISI0081 device with _UID + * matching our segment. + */ + res = devm_kzalloc(dev, sizeof(*res), GFP_KERNEL); + if (!res) + return -ENOMEM; + + ret = acpi_get_rc_resources(dev, "HISI0081", root->segment, res); + if (ret) { + dev_err(dev, "can't get rc base address\n"); + return -ENOMEM; + } + + reg_base = devm_ioremap(dev, res->start, resource_size(res)); + if (!reg_base) + return -ENOMEM; + + cfg->priv = reg_base; + return 0; +} + +struct pci_ecam_ops hisi_pcie_ops = { + .bus_shift = 20, + .init = hisi_pcie_init, + .pci_ops = { + .map_bus = hisi_pcie_map_bus, + .read = hisi_pcie_rd_conf, + .write = hisi_pcie_wr_conf, + } +}; + +#endif + +#ifdef CONFIG_PCI_HISI + +#include "pcie-designware.h" + +#define PCIE_SUBCTRL_SYS_STATE4_REG 0x6818 +#define PCIE_HIP06_CTRL_OFF 0x1000 +#define PCIE_SYS_STATE4 (PCIE_HIP06_CTRL_OFF + 0x31c) +#define PCIE_LTSSM_LINKUP_STATE 0x11 +#define PCIE_LTSSM_STATE_MASK 0x3F + +#define to_hisi_pcie(x) dev_get_drvdata((x)->dev) + +struct hisi_pcie; + +struct pcie_soc_ops { + int (*hisi_pcie_link_up)(struct hisi_pcie *hisi_pcie); +}; + +struct hisi_pcie { + struct dw_pcie *pci; + struct regmap *subctrl; + u32 port_id; + const struct pcie_soc_ops *soc_ops; +}; + +/* HipXX PCIe host only supports 32-bit config access */ +static int hisi_pcie_cfg_read(struct pcie_port *pp, int where, int size, + u32 *val) +{ + u32 reg; + u32 reg_val; + void *walker = ®_val; + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + + walker += (where & 0x3); + reg = where & ~0x3; + reg_val = dw_pcie_readl_dbi(pci, reg); + + if (size == 1) + *val = *(u8 __force *) walker; + else if (size == 2) + *val = *(u16 __force *) walker; + else if (size == 4) + *val = reg_val; + else + return PCIBIOS_BAD_REGISTER_NUMBER; + + return PCIBIOS_SUCCESSFUL; +} + +/* HipXX PCIe host only supports 32-bit config access */ +static int hisi_pcie_cfg_write(struct pcie_port *pp, int where, int size, + u32 val) +{ + u32 reg_val; + u32 reg; + void *walker = ®_val; + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + + walker += (where & 0x3); + reg = where & ~0x3; + if (size == 4) + dw_pcie_writel_dbi(pci, reg, val); + else if (size == 2) { + reg_val = dw_pcie_readl_dbi(pci, reg); + *(u16 __force *) walker = val; + dw_pcie_writel_dbi(pci, reg, reg_val); + } else if (size == 1) { + reg_val = dw_pcie_readl_dbi(pci, reg); + *(u8 __force *) walker = val; + dw_pcie_writel_dbi(pci, reg, reg_val); + } else + return PCIBIOS_BAD_REGISTER_NUMBER; + + return PCIBIOS_SUCCESSFUL; +} + +static int hisi_pcie_link_up_hip05(struct hisi_pcie *hisi_pcie) +{ + u32 val; + + regmap_read(hisi_pcie->subctrl, PCIE_SUBCTRL_SYS_STATE4_REG + + 0x100 * hisi_pcie->port_id, &val); + + return ((val & PCIE_LTSSM_STATE_MASK) == PCIE_LTSSM_LINKUP_STATE); +} + +static int hisi_pcie_link_up_hip06(struct hisi_pcie *hisi_pcie) +{ + struct dw_pcie *pci = hisi_pcie->pci; + u32 val; + + val = dw_pcie_readl_dbi(pci, PCIE_SYS_STATE4); + + return ((val & PCIE_LTSSM_STATE_MASK) == PCIE_LTSSM_LINKUP_STATE); +} + +static int hisi_pcie_link_up(struct dw_pcie *pci) +{ + struct hisi_pcie *hisi_pcie = to_hisi_pcie(pci); + + return hisi_pcie->soc_ops->hisi_pcie_link_up(hisi_pcie); +} + +static struct dw_pcie_host_ops hisi_pcie_host_ops = { + .rd_own_conf = hisi_pcie_cfg_read, + .wr_own_conf = hisi_pcie_cfg_write, +}; + +static int hisi_add_pcie_port(struct hisi_pcie *hisi_pcie, + struct platform_device *pdev) +{ + struct dw_pcie *pci = hisi_pcie->pci; + struct pcie_port *pp = &pci->pp; + struct device *dev = &pdev->dev; + int ret; + u32 port_id; + + if (of_property_read_u32(dev->of_node, "port-id", &port_id)) { + dev_err(dev, "failed to read port-id\n"); + return -EINVAL; + } + if (port_id > 3) { + dev_err(dev, "Invalid port-id: %d\n", port_id); + return -EINVAL; + } + hisi_pcie->port_id = port_id; + + pp->ops = &hisi_pcie_host_ops; + + ret = dw_pcie_host_init(pp); + if (ret) { + dev_err(dev, "failed to initialize host\n"); + return ret; + } + + return 0; +} + +static const struct dw_pcie_ops dw_pcie_ops = { + .link_up = hisi_pcie_link_up, +}; + +static int hisi_pcie_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dw_pcie *pci; + struct hisi_pcie *hisi_pcie; + struct resource *reg; + struct device_driver *driver; + int ret; + + hisi_pcie = devm_kzalloc(dev, sizeof(*hisi_pcie), GFP_KERNEL); + if (!hisi_pcie) + return -ENOMEM; + + pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL); + if (!pci) + return -ENOMEM; + + pci->dev = dev; + pci->ops = &dw_pcie_ops; + + driver = dev->driver; + + hisi_pcie->soc_ops = of_device_get_match_data(dev); + + hisi_pcie->subctrl = + syscon_regmap_lookup_by_compatible("hisilicon,pcie-sas-subctrl"); + if (IS_ERR(hisi_pcie->subctrl)) { + dev_err(dev, "cannot get subctrl base\n"); + return PTR_ERR(hisi_pcie->subctrl); + } + + reg = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rc_dbi"); + pci->dbi_base = devm_ioremap_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); + if (ret) + return ret; + + return 0; +} + +static struct pcie_soc_ops hip05_ops = { + &hisi_pcie_link_up_hip05 +}; + +static struct pcie_soc_ops hip06_ops = { + &hisi_pcie_link_up_hip06 +}; + +static const struct of_device_id hisi_pcie_of_match[] = { + { + .compatible = "hisilicon,hip05-pcie", + .data = (void *) &hip05_ops, + }, + { + .compatible = "hisilicon,hip06-pcie", + .data = (void *) &hip06_ops, + }, + {}, +}; + +static struct platform_driver hisi_pcie_driver = { + .probe = hisi_pcie_probe, + .driver = { + .name = "hisi-pcie", + .of_match_table = hisi_pcie_of_match, + }, +}; +builtin_platform_driver(hisi_pcie_driver); + +static int hisi_pcie_almost_ecam_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pci_ecam_ops *ops; + + ops = (struct pci_ecam_ops *)of_device_get_match_data(dev); + return pci_host_common_probe(pdev, ops); +} + +static int hisi_pcie_platform_init(struct pci_config_window *cfg) +{ + struct device *dev = cfg->parent; + struct platform_device *pdev = to_platform_device(dev); + struct resource *res; + void __iomem *reg_base; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) { + dev_err(dev, "missing \"reg[1]\"property\n"); + return -EINVAL; + } + + reg_base = devm_ioremap(dev, res->start, resource_size(res)); + if (!reg_base) + return -ENOMEM; + + cfg->priv = reg_base; + return 0; +} + +struct pci_ecam_ops hisi_pcie_platform_ops = { + .bus_shift = 20, + .init = hisi_pcie_platform_init, + .pci_ops = { + .map_bus = hisi_pcie_map_bus, + .read = hisi_pcie_rd_conf, + .write = hisi_pcie_wr_conf, + } +}; + +static const struct of_device_id hisi_pcie_almost_ecam_of_match[] = { + { + .compatible = "hisilicon,pcie-almost-ecam", + .data = (void *) &hisi_pcie_platform_ops, + }, + {}, +}; + +static struct platform_driver hisi_pcie_almost_ecam_driver = { + .probe = hisi_pcie_almost_ecam_probe, + .driver = { + .name = "hisi-pcie-almost-ecam", + .of_match_table = hisi_pcie_almost_ecam_of_match, + }, +}; +builtin_platform_driver(hisi_pcie_almost_ecam_driver); + +#endif +#endif |