blob: dbc452382c3c43f63a5a9a39ceff1b5d17b11e9a [file] [log] [blame]
// 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);