From patchwork Thu Mar 23 13:05:51 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mason X-Patchwork-Id: 9641047 X-Patchwork-Delegate: bhelgaas@google.com Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id DF192602CA for ; Thu, 23 Mar 2017 13:06:26 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id CDF8228445 for ; Thu, 23 Mar 2017 13:06:26 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id BF773284D5; Thu, 23 Mar 2017 13:06:26 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00,FREEMAIL_FROM, RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id BDAD828445 for ; Thu, 23 Mar 2017 13:06:25 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932449AbdCWNGY (ORCPT ); Thu, 23 Mar 2017 09:06:24 -0400 Received: from smtp5-g21.free.fr ([212.27.42.5]:35324 "EHLO smtp5-g21.free.fr" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752389AbdCWNGU (ORCPT ); Thu, 23 Mar 2017 09:06:20 -0400 Received: from [172.27.0.114] (unknown [92.154.11.170]) (Authenticated sender: slash.tmp) by smtp5-g21.free.fr (Postfix) with ESMTPSA id 56E305FF9A; Thu, 23 Mar 2017 14:05:51 +0100 (CET) From: Mason Subject: [RFC PATCH v0.2] PCI: Add support for tango PCIe host bridge To: Bjorn Helgaas , Marc Zyngier , Thomas Gleixner Cc: Robin Murphy , Lorenzo Pieralisi , Liviu Dudau , David Laight , linux-pci , Linux ARM , Thibaud Cornic , Phuong Nguyen , LKML Message-ID: <91db1f47-3024-9712-309a-fb4b21e42028@free.fr> Date: Thu, 23 Mar 2017 14:05:51 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:51.0) Gecko/20100101 Firefox/51.0 SeaMonkey/2.48 MIME-Version: 1.0 Sender: linux-pci-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pci@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP I think this version is ready for review. It has all the required bits and pieces. I still have a few questions, embedded as comments in the code. (Missing are ancillary changes to Kconfig, Makefile) --- drivers/pci/host/pcie-tango.c | 350 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 350 insertions(+) create mode 100644 drivers/pci/host/pcie-tango.c diff --git a/drivers/pci/host/pcie-tango.c b/drivers/pci/host/pcie-tango.c new file mode 100644 index 000000000000..b2e6448aed2d --- /dev/null +++ b/drivers/pci/host/pcie-tango.c @@ -0,0 +1,350 @@ +#include +#include +#include +#include + +#define MSI_COUNT 32 + +struct tango_pcie { + void __iomem *mux; + void __iomem *msi_status; + void __iomem *msi_mask; + phys_addr_t msi_doorbell; + struct mutex lock; /* lock for updating msi_mask */ + struct irq_domain *irq_domain; + struct irq_domain *msi_domain; + int irq; +}; + +/*** MSI CONTROLLER SUPPORT ***/ + +static void tango_msi_isr(struct irq_desc *desc) +{ + struct irq_chip *chip = irq_desc_get_chip(desc); + struct tango_pcie *pcie; + unsigned long status, virq; + int pos; + + chained_irq_enter(chip, desc); + pcie = irq_desc_get_handler_data(desc); + + status = readl_relaxed(pcie->msi_status); + writel_relaxed(status, pcie->msi_status); /* clear IRQs */ + + for_each_set_bit(pos, &status, MSI_COUNT) { + virq = irq_find_mapping(pcie->irq_domain, pos); + if (virq) + generic_handle_irq(virq); + else + pr_err("Unhandled MSI: %d\n", pos); + } + + chained_irq_exit(chip, desc); +} + +static struct irq_chip tango_msi_irq_chip = { + .name = "MSI", + .irq_mask = pci_msi_mask_irq, + .irq_unmask = pci_msi_unmask_irq, +}; + +static struct msi_domain_info msi_domain_info = { + .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS, + .chip = &tango_msi_irq_chip, +}; + +static void tango_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) +{ + struct tango_pcie *pcie = irq_data_get_irq_chip_data(data); + + msg->address_lo = lower_32_bits(pcie->msi_doorbell); + msg->address_hi = upper_32_bits(pcie->msi_doorbell); + msg->data = data->hwirq; +} + +static int tango_set_affinity(struct irq_data *irq_data, + const struct cpumask *mask, bool force) +{ + return -EINVAL; +} + +static struct irq_chip tango_msi_chip = { + .name = "MSI", + .irq_compose_msi_msg = tango_compose_msi_msg, + .irq_set_affinity = tango_set_affinity, +}; + +static int tango_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs, void *args) +{ + struct tango_pcie *pcie = domain->host_data; + int pos, err = 0; + u32 mask; + + if (nr_irqs != 1) /* When does that happen? */ + return -EINVAL; + + mutex_lock(&pcie->lock); + + mask = readl_relaxed(pcie->msi_mask); + pos = find_first_zero_bit(&mask, MSI_COUNT); + if (pos < MSI_COUNT) + writel(mask | BIT(pos), pcie->msi_mask); + else + err = -ENOSPC; + + mutex_unlock(&pcie->lock); + + irq_domain_set_info(domain, virq, pos, &tango_msi_chip, + domain->host_data, handle_simple_irq, NULL, NULL); + + return err; +} + +static void tango_irq_domain_free(struct irq_domain *domain, + unsigned int virq, unsigned int nr_irqs) +{ + struct irq_data *d = irq_domain_get_irq_data(domain, virq); + struct tango_pcie *pcie = irq_data_get_irq_chip_data(d); + int pos = d->hwirq; + u32 mask; + + mutex_lock(&pcie->lock); + + mask = readl(pcie->msi_mask); + writel(mask & ~BIT(pos), pcie->msi_mask); + + mutex_unlock(&pcie->lock); +} + +static const struct irq_domain_ops msi_domain_ops = { + .alloc = tango_irq_domain_alloc, + .free = tango_irq_domain_free, +}; + +static int tango_msi_remove(struct platform_device *pdev) +{ + struct tango_pcie *msi = platform_get_drvdata(pdev); + + irq_set_chained_handler(msi->irq, NULL); + irq_set_handler_data(msi->irq, NULL); + /* irq_set_chained_handler_and_data(msi->irq, NULL, NULL); instead? */ + + irq_domain_remove(msi->msi_domain); + irq_domain_remove(msi->irq_domain); + + return 0; +} + +static int tango_msi_probe(struct platform_device *pdev, struct tango_pcie *pcie) +{ + int virq; + struct fwnode_handle *fwnode = of_node_to_fwnode(pdev->dev.of_node); + struct irq_domain *msi_dom, *irq_dom; + + mutex_init(&pcie->lock); + writel(0, pcie->msi_mask); + + /* Why is fwnode for this call? */ + irq_dom = irq_domain_add_linear(NULL, MSI_COUNT, &msi_domain_ops, pcie); + if (!irq_dom) { + pr_err("Failed to create IRQ domain\n"); + return -ENOMEM; + } + + msi_dom = pci_msi_create_irq_domain(fwnode, &msi_domain_info, irq_dom); + if (!msi_dom) { + pr_err("Failed to create MSI domain\n"); + irq_domain_remove(irq_dom); + return -ENOMEM; + } + + virq = platform_get_irq(pdev, 1); + if (virq <= 0) { + irq_domain_remove(msi_dom); + irq_domain_remove(irq_dom); + return -ENXIO; + } + + pcie->irq_domain = irq_dom; + pcie->msi_domain = msi_dom; + pcie->irq = virq; + irq_set_chained_handler_and_data(virq, tango_msi_isr, pcie); + + return 0; +} + +/*** HOST BRIDGE SUPPORT ***/ + +static int smp8759_config_read(struct pci_bus *bus, + unsigned int devfn, int where, int size, u32 *val) +{ + int ret; + struct pci_config_window *cfg = bus->sysdata; + struct tango_pcie *pcie = dev_get_drvdata(cfg->parent); + + /* + * QUIRK #1 + * Reads in configuration space outside devfn 0 return garbage. + */ + if (devfn != 0) { + *val = 0xffffffff; /* ~0 means "nothing here" right? */ + return PCIBIOS_SUCCESSFUL; /* Should we return error or success? */ + } + + /* + * QUIRK #2 + * The root complex advertizes a fake BAR, which is used to filter + * bus-to-system requests. Hide it from Linux. + */ + if (where == PCI_BASE_ADDRESS_0 && bus->number == 0) { + *val = 0; /* 0 or ~0 to hide the BAR from Linux? */ + return PCIBIOS_SUCCESSFUL; /* Should we return error or success? */ + } + + /* + * QUIRK #3 + * Unfortunately, config and mem spaces are muxed. + * Linux does not support such a setting, since drivers are free + * to access mem space directly, at any time. + * Therefore, we can only PRAY that config and mem space accesses + * NEVER occur concurrently. + */ + writel(1, pcie->mux); + ret = pci_generic_config_read(bus, devfn, where, size, val); + writel(0, pcie->mux); + + return ret; +} + +static int smp8759_config_write(struct pci_bus *bus, + unsigned int devfn, int where, int size, u32 val) +{ + int ret; + struct pci_config_window *cfg = bus->sysdata; + struct tango_pcie *pcie = dev_get_drvdata(cfg->parent); + + writel(1, pcie->mux); + ret = pci_generic_config_write(bus, devfn, where, size, val); + writel(0, pcie->mux); + + return ret; +} + +static struct pci_ecam_ops smp8759_ecam_ops = { + .bus_shift = 20, + .pci_ops = { + .map_bus = pci_ecam_map_bus, + .read = smp8759_config_read, + .write = smp8759_config_write, + } +}; + +static const struct of_device_id tango_pcie_ids[] = { + { .compatible = "sigma,smp8759-pcie" }, + { .compatible = "sigma,rev2-pcie" }, + { /* sentinel */ }, +}; + +static void smp8759_init(struct tango_pcie *pcie, void __iomem *base) +{ + pcie->mux = base + 0x48; + pcie->msi_status = base + 0x80; + pcie->msi_mask = base + 0xa0; + pcie->msi_doorbell = 0xa0000000 + 0x2e07c; +} + +static void rev2_init(struct tango_pcie *pcie, void __iomem *base) +{ + void __iomem *misc_irq = base + 0x40; + void __iomem *doorbell = base + 0x8c; + + pcie->mux = base + 0x2c; + pcie->msi_status = base + 0x4c; + pcie->msi_mask = base + 0x6c; + pcie->msi_doorbell = 0x80000000; + + writel(lower_32_bits(pcie->msi_doorbell), doorbell + 0); + writel(upper_32_bits(pcie->msi_doorbell), doorbell + 4); + + /* Enable legacy PCI interrupts */ + writel(BIT(15), misc_irq); + writel(0xf << 4, misc_irq + 4); +} + +static int tango_pcie_probe(struct platform_device *pdev) +{ + int ret; + void __iomem *base; + struct resource *res; + struct tango_pcie *pcie; + struct device *dev = &pdev->dev; + + pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL); + if (!pcie) + return -ENOMEM; + + platform_set_drvdata(pdev, pcie); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + if (of_device_is_compatible(dev->of_node, "sigma,smp8759-pcie")) + smp8759_init(pcie, base); + + if (of_device_is_compatible(dev->of_node, "sigma,rev2-pcie")) + rev2_init(pcie, base); + + ret = tango_msi_probe(pdev, pcie); + if (ret) + return ret; + + return pci_host_common_probe(pdev, &smp8759_ecam_ops); +} + +static int tango_pcie_remove(struct platform_device *pdev) +{ + return tango_msi_remove(pdev); +} + +static struct platform_driver tango_pcie_driver = { + .probe = tango_pcie_probe, + .remove = tango_pcie_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = tango_pcie_ids, + }, +}; + +/* + * This should probably be module_platform_driver ? + */ +builtin_platform_driver(tango_pcie_driver); + +#define VENDOR_SIGMA 0x1105 + +/* + * QUIRK #4 + * The root complex advertizes the wrong device class. + * Header Type 1 is for PCI-to-PCI bridges. + */ +static void tango_fixup_class(struct pci_dev *dev) +{ + dev->class = PCI_CLASS_BRIDGE_PCI << 8; +} +DECLARE_PCI_FIXUP_EARLY(VENDOR_SIGMA, PCI_ANY_ID, tango_fixup_class); + +/* + * QUIRK #5 + * Only transfers within the root complex BAR are forwarded to the host. + * By default, the DMA framework expects that + * PCI address 0x8000_0000 maps to system address 0x8000_0000 + * which is where DRAM0 is mapped. + */ +static void tango_fixup_bar(struct pci_dev *dev) +{ + pci_write_config_dword(dev, PCI_BASE_ADDRESS_0, 0x80000000); +} +DECLARE_PCI_FIXUP_FINAL(VENDOR_SIGMA, PCI_ANY_ID, tango_fixup_bar);