| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * PCIe host controller driver for Marvell Berlin SoCs |
| * |
| * Copyright (C) 2018 Synaptics Incorporated |
| * Copyright (C) 2015 Marvell Technology Group Ltd. |
| * http://www.marvell.com |
| * |
| * Author: Jisheng Zhang <jszhang@kernel.org> |
| */ |
| |
| #include <linux/clk.h> |
| #include <linux/delay.h> |
| #include <linux/gpio/consumer.h> |
| #include <linux/init.h> |
| #include <linux/interrupt.h> |
| #include <linux/kernel.h> |
| #include <linux/of_device.h> |
| #include <linux/pci.h> |
| #include <linux/phy/phy.h> |
| #include <linux/platform_device.h> |
| #include <linux/reset.h> |
| #include <linux/resource.h> |
| #include <linux/signal.h> |
| #include <linux/types.h> |
| |
| #include "pcie-designware.h" |
| |
| #define to_berlin_pcie(x) dev_get_drvdata((x)->dev) |
| |
| #define BERLIN_ATU_OUTB_UNR_REG_OFFSET(region) (0x1000 + (region << 9)) |
| |
| struct berlin_pcie_data { |
| u32 intr_status; |
| u32 intr_status1; |
| u32 intr_mask; |
| u32 intr_mask1; |
| u32 intr_mask_val; |
| u32 ctrl; |
| }; |
| |
| struct berlin_pcie { |
| struct dw_pcie *pci; |
| void __iomem *ctrl; |
| const struct berlin_pcie_data *data; |
| struct gpio_desc *reset_gpio; |
| struct gpio_desc *power_gpio; |
| struct gpio_desc *enable_gpio; |
| struct clk *clk; |
| struct reset_control *rc_rst; |
| struct phy *phy; |
| }; |
| |
| static void berlin_pcie_enable_irq_pulse(struct berlin_pcie *priv) |
| { |
| u32 val = priv->data->intr_mask_val; |
| |
| /* enable INTX interrupt */ |
| writel(val, priv->ctrl + priv->data->intr_mask); |
| return; |
| } |
| |
| static irqreturn_t berlin_pcie_irq_handler(int irq, void *arg) |
| { |
| u32 val; |
| struct berlin_pcie *priv = arg; |
| |
| val = readl(priv->ctrl + priv->data->intr_status); |
| writel(val, priv->ctrl + priv->data->intr_status); |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t berlin_pcie_msi_irq_handler(int irq, void *arg) |
| { |
| struct berlin_pcie *priv = arg; |
| struct dw_pcie *pci = priv->pci; |
| struct pcie_port *pp = &pci->pp; |
| |
| return dw_handle_msi_irq(pp); |
| } |
| |
| #ifdef CONFIG_PCIEAER |
| void berlin_pcie_aerrcerr_isr(struct pci_dev *dev, u32 status) |
| { |
| struct pcie_port *pp = dev->bus->sysdata; |
| struct dw_pcie *pci = to_dw_pcie_from_pp(pp); |
| struct berlin_pcie *priv = to_berlin_pcie(pci); |
| struct phy *phy = priv->phy; |
| int pos = dev->aer_cap; |
| |
| if (!(status & PCI_ERR_ROOT_COR_RCV)) |
| return; |
| |
| udelay(150); |
| |
| pci_read_config_dword(dev, pos + PCI_ERR_COR_STATUS, &status); |
| if (!(status & PCI_ERR_COR_RCVR)) |
| return; |
| |
| status = readl(priv->ctrl + 0x20); |
| status >>= 5; |
| status &= 0x3f; |
| if (status != 0x0D) |
| return; |
| |
| if (phy && phy->ops->reset) |
| phy->ops->reset(phy); |
| } |
| #endif |
| |
| static int berlin_pcie_host_init(struct pcie_port *pp) |
| { |
| u32 val; |
| int ret; |
| struct dw_pcie *pci = to_dw_pcie_from_pp(pp); |
| struct berlin_pcie *priv = to_berlin_pcie(pci); |
| |
| /* reset RC */ |
| reset_control_assert(priv->rc_rst); |
| udelay(100); |
| |
| /* disable link training */ |
| val = readl(priv->ctrl + priv->data->ctrl); |
| val &= ~(1 << 5); |
| writel(val, priv->ctrl + priv->data->ctrl); |
| |
| /* power up EP */ |
| gpiod_set_value_cansleep(priv->power_gpio, 1); |
| msleep(10); |
| |
| gpiod_set_value_cansleep(priv->enable_gpio, 1); |
| msleep(10); |
| |
| reset_control_deassert(priv->rc_rst); |
| udelay(200); |
| |
| val = readl(priv->ctrl + priv->data->ctrl); |
| val &= ~(1 << 21); |
| writel(val, priv->ctrl + priv->data->ctrl); |
| val = readl(priv->ctrl + priv->data->ctrl); |
| |
| readl(priv->ctrl + 0x2c); |
| |
| /* power on phy */ |
| ret = phy_init(priv->phy); |
| if (ret) |
| return ret; |
| |
| val = readl(priv->ctrl + 0x28); |
| val &= ~(1 << 0); |
| val |= (1 << 7); |
| writel(val, priv->ctrl + 0x28); |
| |
| val = readl(priv->ctrl + 0x28); |
| val &= ~(7 << 8); |
| val |= (7 << 8); |
| writel(val, priv->ctrl + 0x28); |
| |
| ret = phy_power_on(priv->phy); |
| if (ret) |
| return ret; |
| |
| /* reset EP */ |
| gpiod_set_value_cansleep(priv->reset_gpio, 1); |
| msleep(100); |
| gpiod_set_value_cansleep(priv->reset_gpio, 0); |
| msleep(100); |
| |
| dw_pcie_setup_rc(pp); |
| |
| /* enable link training */ |
| val = readl(priv->ctrl + priv->data->ctrl); |
| val |= (1 << 5); |
| writel(val, priv->ctrl + priv->data->ctrl); |
| |
| /* wait for link up */ |
| ret = dw_pcie_wait_for_link(pci); |
| if (ret) |
| return ret; |
| |
| berlin_pcie_enable_irq_pulse(priv); |
| if (IS_ENABLED(CONFIG_PCI_MSI)) |
| dw_pcie_msi_init(pp); |
| |
| return 0; |
| } |
| |
| static u32 berlin_pcie_readl_ob_unroll(struct dw_pcie *pci, u32 index, u32 reg) |
| { |
| u32 offset = BERLIN_ATU_OUTB_UNR_REG_OFFSET(index); |
| |
| return dw_pcie_readl_dbi(pci, offset + reg); |
| } |
| |
| static void berlin_pcie_writel_ob_unroll(struct dw_pcie *pci, u32 index, |
| u32 reg, u32 val) |
| { |
| u32 offset = BERLIN_ATU_OUTB_UNR_REG_OFFSET(index); |
| |
| dw_pcie_writel_dbi(pci, offset + reg, val); |
| } |
| |
| static const struct dw_pcie_host_ops berlin_pcie_host_ops = { |
| .host_init = berlin_pcie_host_init, |
| }; |
| |
| static const struct dw_pcie_ops dw_pcie_ops = { |
| .readl_ob_unroll = berlin_pcie_readl_ob_unroll, |
| .writel_ob_unroll = berlin_pcie_writel_ob_unroll, |
| }; |
| |
| static int berlin_add_pcie_port(struct berlin_pcie *priv, |
| struct platform_device *pdev) |
| { |
| int ret; |
| u32 val; |
| struct device *dev = &pdev->dev; |
| struct dw_pcie *pci = priv->pci; |
| struct pcie_port *pp = &pci->pp; |
| |
| writel(0xffffffff, priv->ctrl + priv->data->intr_mask); |
| if (priv->data->intr_mask1) |
| writel(0xffffffff, priv->ctrl + priv->data->intr_mask1); |
| |
| val = readl(priv->ctrl + priv->data->intr_status); |
| writel(val, priv->ctrl + priv->data->intr_status); |
| if (priv->data->intr_status1) { |
| val = readl(priv->ctrl + priv->data->intr_status1); |
| writel(val, priv->ctrl + priv->data->intr_status1); |
| } |
| |
| pp->irq = platform_get_irq(pdev, 0); |
| if (pp->irq < 0) |
| return pp->irq; |
| |
| ret = devm_request_irq(dev, pp->irq, berlin_pcie_irq_handler, |
| IRQF_SHARED | IRQF_NO_THREAD, |
| "berlin-pcie", priv); |
| if (ret) { |
| dev_err(dev, "failed to request irq\n"); |
| return ret; |
| } |
| |
| if (IS_ENABLED(CONFIG_PCI_MSI)) { |
| pp->msi_irq = platform_get_irq(pdev, 1); |
| if (pp->msi_irq < 0) |
| return pp->msi_irq; |
| |
| ret = devm_request_irq(dev, pp->msi_irq, |
| berlin_pcie_msi_irq_handler, |
| IRQF_SHARED | IRQF_NO_THREAD, |
| "berlin-pcie-msi", priv); |
| if (ret) { |
| dev_err(dev, "failed to request msi irq\n"); |
| return ret; |
| } |
| } |
| |
| pp->root_bus_nr = 0; |
| pp->ops = &berlin_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 berlin_pcie_data berlin_pcie = { |
| .intr_status = 0xC, |
| .intr_mask = 0x10, |
| .intr_mask_val = ~((1 << 17) | (1 << 18) | (1 << 19) | (1 << 20)), |
| .ctrl = 0x14, |
| }; |
| |
| static const struct berlin_pcie_data as370_pcie = { |
| .intr_status = 0xC, |
| .intr_status1 = 0x10, |
| .intr_mask = 0x14, |
| .intr_mask1 = 0x18, |
| .intr_mask_val = ~((1 << 19) | (1 << 20) | (1 << 21) | (1 << 22)), |
| .ctrl = 0x1C, |
| }; |
| |
| static const struct of_device_id berlin_pcie_of_match[] = { |
| { |
| .compatible = "marvell,berlin-pcie", |
| .data = &berlin_pcie |
| }, |
| { |
| .compatible = "syna,as370-pcie", |
| .data = &as370_pcie |
| }, |
| {}, |
| }; |
| |
| static int berlin_pcie_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct berlin_pcie *priv; |
| struct dw_pcie *pci; |
| struct pcie_port *pp; |
| struct resource *res; |
| int ret; |
| |
| priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); |
| if (!priv) |
| return -ENOMEM; |
| |
| pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL); |
| if (!pci) |
| return -ENOMEM; |
| |
| pci->dev = dev; |
| pci->ops = &dw_pcie_ops; |
| pp = &pci->pp; |
| priv->pci = pci; |
| priv->data = of_device_get_match_data(dev); |
| |
| priv->rc_rst = devm_reset_control_get(dev, NULL); |
| if (IS_ERR(priv->rc_rst)) |
| return PTR_ERR(priv->rc_rst); |
| |
| priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", |
| GPIOD_OUT_LOW); |
| if (IS_ERR(priv->reset_gpio)) |
| return PTR_ERR(priv->reset_gpio); |
| |
| priv->power_gpio = devm_gpiod_get_optional(dev, "power", |
| GPIOD_OUT_LOW); |
| if (IS_ERR(priv->power_gpio)) |
| return PTR_ERR(priv->power_gpio); |
| |
| priv->enable_gpio = devm_gpiod_get_optional(dev, "enable", |
| GPIOD_OUT_LOW); |
| if (IS_ERR(priv->enable_gpio)) |
| return PTR_ERR(priv->enable_gpio); |
| |
| priv->phy = devm_phy_get(dev, "pcie-phy"); |
| if (IS_ERR(priv->phy)) |
| return PTR_ERR(priv->phy); |
| |
| priv->clk = devm_clk_get(dev, NULL); |
| if (IS_ERR(priv->clk)) { |
| dev_err(dev, "Failed to get pcie rc clock\n"); |
| return PTR_ERR(priv->clk); |
| } |
| ret = clk_prepare_enable(priv->clk); |
| if (ret) |
| return ret; |
| |
| res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi"); |
| pci->dbi_base = devm_ioremap_resource(dev, res); |
| if (IS_ERR(pci->dbi_base)) { |
| ret = PTR_ERR(pci->dbi_base); |
| goto fail_clk; |
| } |
| |
| res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ctrl"); |
| priv->ctrl = devm_ioremap_resource(dev, res); |
| if (IS_ERR(priv->ctrl)) { |
| ret = PTR_ERR(priv->ctrl); |
| goto fail_clk; |
| } |
| |
| platform_set_drvdata(pdev, priv); |
| |
| ret = berlin_add_pcie_port(priv, pdev); |
| if (ret < 0) |
| goto fail_clk; |
| |
| return 0; |
| |
| fail_clk: |
| clk_disable_unprepare(priv->clk); |
| return ret; |
| } |
| |
| static struct platform_driver berlin_pcie_driver = { |
| .probe = berlin_pcie_probe, |
| .driver = { |
| .name = "berlin-pcie", |
| .suppress_bind_attrs = true, |
| .of_match_table = berlin_pcie_of_match, |
| }, |
| }; |
| builtin_platform_driver(berlin_pcie_driver); |