From patchwork Thu May 15 16:01:32 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Murali Karicheri X-Patchwork-Id: 4183741 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork2.web.kernel.org (Postfix) with ESMTP id E41FDBFF02 for ; Thu, 15 May 2014 16:07:01 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 77BA920121 for ; Thu, 15 May 2014 16:07:00 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 189E1200EC for ; Thu, 15 May 2014 16:06:59 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1Wky6x-0006Wy-0G; Thu, 15 May 2014 16:02:23 +0000 Received: from arroyo.ext.ti.com ([192.94.94.40]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1Wky6Z-00069Y-Im for linux-arm-kernel@lists.infradead.org; Thu, 15 May 2014 16:02:01 +0000 Received: from dflxv15.itg.ti.com ([128.247.5.124]) by arroyo.ext.ti.com (8.13.7/8.13.7) with ESMTP id s4FG1b2L022138; Thu, 15 May 2014 11:01:37 -0500 Received: from DFLE72.ent.ti.com (dfle72.ent.ti.com [128.247.5.109]) by dflxv15.itg.ti.com (8.14.3/8.13.8) with ESMTP id s4FG1bbV006652; Thu, 15 May 2014 11:01:37 -0500 Received: from dflp32.itg.ti.com (10.64.6.15) by DFLE72.ent.ti.com (128.247.5.109) with Microsoft SMTP Server id 14.3.174.1; Thu, 15 May 2014 11:01:37 -0500 Received: from localhost.localdomain (ileax41-snat.itg.ti.com [10.172.224.153]) by dflp32.itg.ti.com (8.14.3/8.13.8) with ESMTP id s4FG1XJN013438; Thu, 15 May 2014 11:01:37 -0500 From: Murali Karicheri To: , , Subject: [PATCH v1 5/5] pci: keystone: add pcie driver based on designware core driver Date: Thu, 15 May 2014 12:01:32 -0400 Message-ID: <1400169692-9677-6-git-send-email-m-karicheri2@ti.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1400169692-9677-1-git-send-email-m-karicheri2@ti.com> References: <1400169692-9677-1-git-send-email-m-karicheri2@ti.com> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20140515_090159_805196_A8B06857 X-CRM114-Status: GOOD ( 27.14 ) X-Spam-Score: -5.7 (-----) Cc: Grygorii Strashko , Mohit Kumar , Jingoo Han , Murali Karicheri , Santosh Shilimkar , Bjorn Helgaas X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-2.5 required=5.0 tests=BAYES_00,RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP keystone pcie hardware is based on designware version 3.65. This driver make use of the functions from pci-dw-old.c and pci-dw-old-msi.c to implement the driver. Driver mainly handle the platform specific part of the PCI driver and depends on DW Old driver to configure application specific registers. Also routes the irq events and ack the interrupt after the same is acked by the end point device driver. This requires irqchip implementation for legacy and MSI irq handling. This patch adds a quirks to override the max read request size as PCI controller has a limit of 256 bytes. CC: Santosh Shilimkar CC: Mohit Kumar CC: Jingoo Han CC: Bjorn Helgaas Signed-off-by: Murali Karicheri Signed-off-by: Grygorii Strashko --- .../devicetree/bindings/pci/pcie-keystone.txt | 68 ++++ drivers/pci/host/Kconfig | 8 + drivers/pci/host/Makefile | 1 + drivers/pci/host/pci-keystone.c | 400 ++++++++++++++++++++ drivers/pci/quirks.c | 13 + 5 files changed, 490 insertions(+) create mode 100644 Documentation/devicetree/bindings/pci/pcie-keystone.txt create mode 100644 drivers/pci/host/pci-keystone.c diff --git a/Documentation/devicetree/bindings/pci/pcie-keystone.txt b/Documentation/devicetree/bindings/pci/pcie-keystone.txt new file mode 100644 index 0000000..17cf261 --- /dev/null +++ b/Documentation/devicetree/bindings/pci/pcie-keystone.txt @@ -0,0 +1,68 @@ +Keystone PCIE Root complex device tree bindings +----------------------------------------------- + +Sample bindings shown below:- + + - Remove ti,enable-linktrain if boot loader already does Link training and do EP + configuration. + - Remove ti,init-phy if boot loader already initialize the phy and sets up pcie + link + + pcie0_phy: pciephy@2320000 { + #address-cells = <1>; + #size-cells = <1>; + #phy-cells = <0>; + compatible = "ti,keystone-phy"; + reg = <0x02320000 0x4000>; + reg-names = "reg_serdes"; + }; + + pcie@21800000 { + compatible = "ti,keystone-pcie"; + device_type = "pci"; + clocks = <&clkpcie>; + clock-names = "pcie"; + #address-cells = <3>; + #size-cells = <2>; + reg = <0x21800000 0x1000>, <0x0262014c 4>; + reg-names = "reg_rc_app", "reg_devcfg"; + interrupts = , + , + , + , + , + , + , + , + ; /* Error IRQ */ + + ranges = <0x00000800 0 0x21801000 0x21801000 0 0x0002000 /* Configuration space */ + 0x81000000 0 0 0x24000000 0 0x4000 /* downstream I/O */ + 0x82000000 0 0x50000000 0x50000000 0 0x10000000>; /* non-prefetchable memory */ + + num-lanes = <2>; + ti,enable-linktrain; + ti,init-phy; + + /* PCIE phy */ + phys = <&pcie0_phy>; + phy-names = "pcie-phy"; + + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 0>; + interrupt-map = <0 0 0 1 &pcie_intc 1>, // INT A + <0 0 0 2 &pcie_intc 2>, // INT B + <0 0 0 3 &pcie_intc 3>, // INT C + <0 0 0 4 &pcie_intc 4>; // INT D + + pcie_intc: legacy-interrupt-controller { + interrupt-controller; + #interrupt-cells = <1>; + interrupt-parent = <&gic>; + interrupts = , + , + , + ; + }; + }; + diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index c4f4732..066d611 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -37,4 +37,12 @@ config PCI_RCAR_GEN2 Say Y here if you want internal PCI support on R-Car Gen2 SoC. There are 3 internal PCI controllers available with a single built-in EHCI/OHCI host controller present on each one. + +config PCI_KEYSTONE + bool "TI Keystone PCIe controller" + depends on ARCH_KEYSTONE + select PCIE_DW + select PCI_DW_OLD + select PCIEPORTBUS + select PHY_TI_KEYSTONE endmenu diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index be5d939..10e02f9 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -5,3 +5,4 @@ obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o obj-$(CONFIG_PCI_TEGRA) += pci-tegra.o obj-$(CONFIG_PCI_RCAR_GEN2) += pci-rcar-gen2.o obj-$(CONFIG_PCI_DW_OLD) += pci-dw-old-msi.o pci-dw-old.o +obj-$(CONFIG_PCI_KEYSTONE) += pci-keystone.o diff --git a/drivers/pci/host/pci-keystone.c b/drivers/pci/host/pci-keystone.c new file mode 100644 index 0000000..2636717 --- /dev/null +++ b/drivers/pci/host/pci-keystone.c @@ -0,0 +1,400 @@ +/* + * PCIe host controller driver for Texas Instruments Keystone SoCs + * + * Copyright (C) 2013-2014 Texas Instruments., Ltd. + * http://www.ti.com + * + * Author: Murali Karicheri + * Implementation based on pci-exynos.c and pcie-designware.c + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcie-designware.h" +#include "pci-dw-old.h" + +#define DRIVER_NAME "keystone-pcie" + +/* driver specific constants */ +#define MAX_MSI_HOST_IRQS 8 +#define MAX_LEGACY_HOST_IRQS 4 + +/* RC mode settings */ +#define PCIE_RC_MODE (BIT(2)) +#define PCIE_MODE_MASK (BIT(1) | BIT(2)) + +static inline struct pcie_port *sys_to_pcie(struct pci_sys_data *sys) +{ + return sys->private_data; +} + +struct keystone_pcie { + struct clk *clk; + int en_link_train; + struct pcie_port pp; + + int num_legacy_host_irqs; + int legacy_host_irqs[MAX_LEGACY_HOST_IRQS]; + struct device_node *np_intc; + + int num_msi_host_irqs; + int msi_host_irqs[MAX_MSI_HOST_IRQS]; +}; + +#define to_keystone_pcie(x) container_of(x, struct keystone_pcie, pp) + +static int ks_pcie_establish_link(struct keystone_pcie *ks_pcie) +{ + struct pcie_port *pp = &ks_pcie->pp; + int count = 200; + + dw_pcie_setup_rc(pp); + + /* check if the link is up or not */ + while (!dw_pcie_link_up(pp)) { + usleep_range(100, 1000); + if (--count) + continue; + dev_err(pp->dev, "phy link never came up\n"); + return -EINVAL; + } + + return 0; +} + +static void ks_pcie_msi_irq_handler(unsigned int irq, struct irq_desc *desc) +{ + struct keystone_pcie *ks_pcie = irq_desc_get_handler_data(desc); + u32 offset = irq - ks_pcie->msi_host_irqs[0]; + struct pcie_port *pp = &ks_pcie->pp; + struct irq_chip *chip = irq_desc_get_chip(desc); + + dev_dbg(pp->dev, "ks_pcie_msi_irq_handler, irq %d\n", irq); + + /* + * The chained irq handler installation would have replaced normal + * interrupt driver handler so we need to take care of mask/unmask and + * ack operation. + */ + chained_irq_enter(chip, desc); + dw_old_handle_msi_irq(pp, offset); + chained_irq_exit(chip, desc); +} + +/** + * ks_pcie_legacy_irq_handler() - Handle legacy interrupt + * @irq: IRQ line for legacy interrupts + * @desc: Pointer to irq descriptor + * + * Traverse through pending legacy interrupts and invoke handler for each. Also + * takes care of interrupt controller level mask/ack operation. + */ +static void ks_pcie_legacy_irq_handler(unsigned int irq, struct irq_desc *desc) +{ + struct keystone_pcie *ks_pcie = irq_desc_get_handler_data(desc); + u32 irq_offset = irq - ks_pcie->legacy_host_irqs[0]; + struct irq_chip *chip = irq_desc_get_chip(desc); + struct pcie_port *pp = &ks_pcie->pp; + + dev_dbg(ks_pcie->pp.dev, ": Handling legacy irq %d\n", irq); + + /* + * The chained irq handler installation would have replaced normal + * interrupt driver handler so we need to take care of mask/unmask and + * ack operation. + */ + chained_irq_enter(chip, desc); + dw_old_handle_legacy_irq(pp, irq_offset); + chained_irq_exit(chip, desc); +} + +static int ks_pcie_init_legacy_irqs(struct keystone_pcie *ks_pcie) +{ + struct device *dev = ks_pcie->pp.dev; + struct device_node *np_pcie = dev->of_node; + int num_legacy_irqs; + int ret = 0; + int i; + + /* get node */ + ks_pcie->np_intc = of_find_node_by_name(np_pcie, + "legacy-interrupt-controller"); + if (!ks_pcie->np_intc) { + dev_err(dev, + "Node for legacy-interrupt-controller is absent\n"); + goto out; + } + + /* get number of IRQs */ + num_legacy_irqs = of_irq_count(ks_pcie->np_intc); + if (!num_legacy_irqs) + goto out; + + if (num_legacy_irqs > MAX_LEGACY_HOST_IRQS) { + dev_err(dev, "Too many legacy interrupts defined %u\n", + num_legacy_irqs); + num_legacy_irqs = MAX_LEGACY_HOST_IRQS; + } + /* + * support upto MAX_LEGACY_HOST_IRQS host and legacy IRQs. + * In dt from index 0 to 3 + */ + for (i = 0; i < num_legacy_irqs; i++) { + ks_pcie->legacy_host_irqs[i] = + irq_of_parse_and_map(ks_pcie->np_intc, i); + if (ks_pcie->legacy_host_irqs[i] < 0) + break; + ks_pcie->num_legacy_host_irqs++; + } + + if (!ks_pcie->num_legacy_host_irqs) { + dev_err(dev, "Failed to get legacy interrupts\n"); + goto out; + } + +out: + return ret; +} + +static void ks_pcie_enable_interrupts(struct keystone_pcie *ks_pcie) +{ + struct pcie_port *pp = &ks_pcie->pp; + int i; + + /* Legacy IRQ */ + for (i = 0; i < ks_pcie->num_legacy_host_irqs; i++) { + irq_set_handler_data(ks_pcie->legacy_host_irqs[i], ks_pcie); + irq_set_chained_handler(ks_pcie->legacy_host_irqs[i], + ks_pcie_legacy_irq_handler); + } + dw_old_enable_legacy_irqs(pp); + + /* MSI IRQ */ + if (IS_ENABLED(CONFIG_PCI_MSI)) { + for (i = 0; i < ks_pcie->num_msi_host_irqs; i++) { + irq_set_chained_handler(ks_pcie->msi_host_irqs[i], + ks_pcie_msi_irq_handler); + irq_set_handler_data(ks_pcie->msi_host_irqs[i], + ks_pcie); + + } + } + + return; +} + +static int +keystone_pcie_fault(unsigned long addr, unsigned int fsr, + struct pt_regs *regs) +{ + unsigned long instr = *(unsigned long *) instruction_pointer(regs); + + if ((instr & 0x0e100090) == 0x00100090) { + int reg = (instr >> 12) & 15; + + regs->uregs[reg] = -1; + regs->ARM_pc += 4; + } + + return 0; +} + +static void __init ks_pcie_host_init(struct pcie_port *pp) +{ + struct keystone_pcie *ks_pcie = to_keystone_pcie(pp); + + ks_pcie_establish_link(ks_pcie); + dw_old_disable_bars(pp); + dw_old_setup_ob_regs(pp); + ks_pcie_enable_interrupts(ks_pcie); + writew(PCI_IO_RANGE_TYPE_32 | (PCI_IO_RANGE_TYPE_32 << 8), + pp->dbi_base + PCI_IO_BASE); + /* + * PCIe access errors that result into OCP errors are caught by ARM as + * "External aborts" (Precise). + */ + hook_fault_code(17, keystone_pcie_fault, SIGBUS, 0, + "external abort on linefetch"); +} + +int ks_pcie_link_up(struct pcie_port *pp) +{ + struct keystone_pcie *ks_pcie = to_keystone_pcie(pp); + + return dw_old_pcie_link_up(pp, ks_pcie->en_link_train); +} + +static struct pcie_host_ops keystone_pcie_host_ops = { + .rd_other_conf = dw_old_rd_other_conf, + .wr_other_conf = dw_old_wr_other_conf, + .link_up = ks_pcie_link_up, + .host_init = ks_pcie_host_init, + .get_msi_data = dw_old_get_msi_data, +}; + +static int add_pcie_port(struct keystone_pcie *ks_pcie, + struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct pcie_port *pp = &ks_pcie->pp; + struct resource *res; + int ret, i; + + ret = ks_pcie_init_legacy_irqs(ks_pcie); + if (ret) + return ret; + + if (IS_ENABLED(CONFIG_PCI_MSI)) { + /* + * support upto 32 MSI irqs mapped to 8 host IRQs. + * In dt from index 4 to 11 + */ + for (i = 0; i < MAX_MSI_HOST_IRQS; i++) { + ks_pcie->msi_host_irqs[i] = irq_of_parse_and_map(np, i); + if (ks_pcie->msi_host_irqs[i] < 0) + break; + ks_pcie->num_msi_host_irqs++; + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "reg_rc_app"); + pp->va_app_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(pp->va_app_base)) + return PTR_ERR(pp->va_app_base); + pp->app_base = res->start; + + pp->root_bus_nr = -1; + pp->version = DW_VERSION_OLD; + pp->ops = &keystone_pcie_host_ops; + spin_lock_init(&pp->conf_lock); + ret = dw_old_pcie_host_init(pp, ks_pcie->np_intc); + if (ret) { + dev_err(&pdev->dev, "failed to initialize host\n"); + return ret; + } + + return ret; +} + +static const struct of_device_id ks_pcie_of_match[] = { + { + .type = "pci", + .compatible = "ti,keystone-pcie", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, ks_pcie_of_match); + +static int __exit ks_pcie_remove(struct platform_device *pdev) +{ + struct keystone_pcie *ks_pcie = platform_get_drvdata(pdev); + + clk_disable_unprepare(ks_pcie->clk); + + return 0; +} + +static int __init ks_pcie_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct keystone_pcie *ks_pcie; + void __iomem *devstat; + struct pcie_port *pp; + struct resource *res; + struct phy *phy; + int ret = 0; + u32 val; + + ks_pcie = devm_kzalloc(&pdev->dev, sizeof(*ks_pcie), + GFP_KERNEL); + if (!ks_pcie) { + dev_err(dev, "no memory for keystone pcie\n"); + return -ENOMEM; + } + + /* check if serdes phy needs to be enabled */ + if (of_get_property(np, "ti,init-phy", NULL) != NULL) { + phy = devm_phy_get(dev, "pcie-phy"); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + ret = phy_init(phy); + if (ret < 0) + return ret; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "reg_devcfg"); + devstat = devm_ioremap_resource(dev, res); + if (IS_ERR(devstat)) + return PTR_ERR(devstat); + + /* enable RC mode in devcfg */ + val = readl(devstat); + val &= ~PCIE_MODE_MASK; + val |= PCIE_RC_MODE; + writel(val, devstat); + + /* check if we need to enable link training */ + ks_pcie->en_link_train = + (of_get_property(np, "ti,enable-linktrain", NULL) != NULL); + + pp = &ks_pcie->pp; + pp->dev = dev; + + ks_pcie->clk = devm_clk_get(dev, "pcie"); + if (IS_ERR(ks_pcie->clk)) { + dev_err(dev, "Failed to get pcie rc clock\n"); + return PTR_ERR(ks_pcie->clk); + } + ret = clk_prepare_enable(ks_pcie->clk); + if (ret) + return ret; + + ret = add_pcie_port(ks_pcie, pdev); + if (ret < 0) + goto fail_clk; + + platform_set_drvdata(pdev, ks_pcie); + dev_info(dev, "pcie rc probe success\n"); + + return 0; + +fail_clk: + clk_disable_unprepare(ks_pcie->clk); + + return ret; +} + +static struct platform_driver ks_pcie_driver __refdata = { + .probe = ks_pcie_probe, + .remove = __exit_p(ks_pcie_remove), + .driver = { + .name = "keystone-pcie", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(ks_pcie_of_match), + }, +}; + +module_platform_driver(ks_pcie_driver); + +MODULE_AUTHOR("Murali Karicheri "); +MODULE_DESCRIPTION("Keystone PCIe host controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index e729206..b3e12c2 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -3651,3 +3651,16 @@ void pci_dev_specific_enable_acs(struct pci_dev *dev) } } } +#ifdef CONFIG_PCI_KEYSTONE +/* + * The KeyStone PCIe controller has maximum read request size of 256 bytes. + */ +static void quirk_limit_readrequest(struct pci_dev *dev) +{ + int readrq = pcie_get_readrq(dev); + + if (readrq > 256) + pcie_set_readrq(dev, 256); +} +DECLARE_PCI_FIXUP_FINAL(PCI_ANY_ID, PCI_ANY_ID, quirk_limit_readrequest); +#endif /* CONFIG_PCI_KEYSTONE */