09fd0174c5
This patch fixes MSI interrupts missing problem. Signed-off-by: Yao Chen <chenyao11@huawei.com>
426 lines
9.2 KiB
C
426 lines
9.2 KiB
C
/*
|
|
* PCIe host controller driver for Kirin Phone SoCs
|
|
*
|
|
* Copyright (C) 2015 Hilisicon Electronics Co., Ltd.
|
|
* http://www.huawei.com
|
|
*
|
|
* Author: Xiaowei Song <songxiaowei@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 "pcie-kirin.h"
|
|
|
|
struct kirin_pcie *g_kirin_pcie;
|
|
|
|
static void kirin_pcie_sideband_dbi_w_mode(struct pcie_port *pp, bool on)
|
|
{
|
|
u32 val;
|
|
struct kirin_pcie *pcie = to_kirin_pcie(pp);
|
|
|
|
val = kirin_elb_readl(pcie, SOC_PCIECTRL_CTRL0_ADDR);
|
|
if (on)
|
|
val = val | PCIE_ELBI_SLV_DBI_ENABLE;
|
|
else
|
|
val = val & ~PCIE_ELBI_SLV_DBI_ENABLE;
|
|
|
|
kirin_elb_writel(pcie, val, SOC_PCIECTRL_CTRL0_ADDR);
|
|
}
|
|
|
|
static void kirin_pcie_sideband_dbi_r_mode(struct pcie_port *pp, bool on)
|
|
{
|
|
u32 val;
|
|
struct kirin_pcie *pcie = to_kirin_pcie(pp);
|
|
|
|
val = kirin_elb_readl(pcie, SOC_PCIECTRL_CTRL1_ADDR);
|
|
if (on)
|
|
val = val | PCIE_ELBI_SLV_DBI_ENABLE;
|
|
else
|
|
val = val & ~PCIE_ELBI_SLV_DBI_ENABLE;
|
|
|
|
kirin_elb_writel(pcie, val, SOC_PCIECTRL_CTRL1_ADDR);
|
|
}
|
|
|
|
static int kirin_pcie_rd_own_conf(struct pcie_port *pp,
|
|
int where, int size, u32 *val)
|
|
{
|
|
kirin_pcie_sideband_dbi_r_mode(pp, true);
|
|
|
|
*val = readl(pp->dbi_base + (where & ~0x3));
|
|
if (size == 1)
|
|
*val = (*val >> (8 * (where & 3))) & 0xff;
|
|
else if (size == 2)
|
|
*val = (*val >> (8 * (where & 3))) & 0xffff;
|
|
else if (size != 4)
|
|
return PCIBIOS_BAD_REGISTER_NUMBER;
|
|
|
|
kirin_pcie_sideband_dbi_r_mode(pp, false);
|
|
|
|
return PCIBIOS_SUCCESSFUL;
|
|
}
|
|
|
|
static int kirin_pcie_wr_own_conf(struct pcie_port *pp,
|
|
int where, int size, u32 val)
|
|
{
|
|
int ret;
|
|
|
|
kirin_pcie_sideband_dbi_w_mode(pp, true);
|
|
|
|
if (size == 4)
|
|
writel(val, pp->dbi_base + (where & ~0x3));
|
|
else if (size == 2)
|
|
writew(val, pp->dbi_base + (where & ~0x3) + (where & 2));
|
|
else if (size == 1)
|
|
writeb(val, pp->dbi_base + (where & ~0x3) + (where & 3));
|
|
else
|
|
return PCIBIOS_BAD_REGISTER_NUMBER;
|
|
|
|
kirin_pcie_sideband_dbi_w_mode(pp, false);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static u32 kirin_pcie_readl_rc(struct pcie_port *pp, u32 reg)
|
|
{
|
|
u32 val;
|
|
|
|
kirin_pcie_sideband_dbi_r_mode(pp, true);
|
|
val = readl(pp->dbi_base + reg);
|
|
kirin_pcie_sideband_dbi_r_mode(pp, false);
|
|
|
|
return val;
|
|
}
|
|
|
|
static void kirin_pcie_writel_rc(struct pcie_port *pp, u32 reg, u32 val)
|
|
{
|
|
kirin_pcie_sideband_dbi_w_mode(pp, true);
|
|
writel(val, pp->dbi_base + reg);
|
|
kirin_pcie_sideband_dbi_w_mode(pp, false);
|
|
}
|
|
|
|
static int kirin_pcie_link_up(struct pcie_port *pp)
|
|
{
|
|
struct kirin_pcie *pcie = to_kirin_pcie(pp);
|
|
u32 val = kirin_elb_readl(pcie, SOC_PCIECTRL_STATE0_ADDR);
|
|
|
|
if ((val & PCIE_LINKUP_ENABLE) == PCIE_LINKUP_ENABLE)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int kirin_pcie_establish_link(struct pcie_port *pp)
|
|
{
|
|
int count = 0;
|
|
|
|
struct kirin_pcie *pcie = to_kirin_pcie(pp);
|
|
|
|
if (kirin_pcie_link_up(pp))
|
|
return 0;
|
|
|
|
dw_pcie_setup_rc(pp);
|
|
|
|
/* assert LTSSM enable */
|
|
kirin_elb_writel(pcie, PCIE_LTSSM_ENABLE_BIT,
|
|
SOC_PCIECTRL_CTRL7_ADDR);
|
|
|
|
/* check if the link is up or not */
|
|
while (!kirin_pcie_link_up(pp)) {
|
|
mdelay(1);
|
|
count++;
|
|
if (count == 1000) {
|
|
dev_err(pp->dev, "Link Fail\n");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static irqreturn_t kirin_pcie_msi_irq_handler(int irq, void *arg)
|
|
{
|
|
struct pcie_port *pp = arg;
|
|
|
|
return dw_handle_msi_irq(pp);
|
|
}
|
|
|
|
static void kirin_pcie_msi_init(struct pcie_port *pp)
|
|
{
|
|
dw_pcie_msi_init(pp);
|
|
|
|
}
|
|
|
|
static void kirin_pcie_enable_interrupts(struct pcie_port *pp)
|
|
{
|
|
if (IS_ENABLED(CONFIG_PCI_MSI))
|
|
kirin_pcie_msi_init(pp);
|
|
}
|
|
|
|
static void kirin_pcie_host_init(struct pcie_port *pp)
|
|
{
|
|
if (kirin_pcie_establish_link(pp))
|
|
return;
|
|
|
|
kirin_pcie_enable_interrupts(pp);
|
|
|
|
return;
|
|
}
|
|
|
|
static struct pcie_host_ops kirin_pcie_host_ops = {
|
|
.readl_rc = kirin_pcie_readl_rc,
|
|
.writel_rc = kirin_pcie_writel_rc,
|
|
.rd_own_conf = kirin_pcie_rd_own_conf,
|
|
.wr_own_conf = kirin_pcie_wr_own_conf,
|
|
.link_up = kirin_pcie_link_up,
|
|
.host_init = kirin_pcie_host_init,
|
|
};
|
|
|
|
static int __init kirin_add_pcie_port(struct pcie_port *pp,
|
|
struct platform_device *pdev)
|
|
{
|
|
int ret;
|
|
|
|
if (IS_ENABLED(CONFIG_PCI_MSI)) {
|
|
pp->msi_irq = platform_get_irq(pdev, 0);
|
|
if (!pp->msi_irq) {
|
|
dev_err(&pdev->dev, "failed to get msi irq\n");
|
|
return -ENODEV;
|
|
}
|
|
ret = devm_request_irq(&pdev->dev, pp->msi_irq,
|
|
kirin_pcie_msi_irq_handler,
|
|
IRQF_SHARED | IRQF_NO_THREAD,
|
|
"kirin_pcie_msi", pp);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "failed to request msi irq\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
pp->root_bus_nr = -1;
|
|
pp->ops = &kirin_pcie_host_ops;
|
|
|
|
ret = dw_pcie_host_init(pp);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int kirin_pcie_save_rc_cfg(struct kirin_pcie *pcie)
|
|
{
|
|
int ret;
|
|
u32 val = 0;
|
|
int aer_pos;
|
|
struct pcie_port *pp;
|
|
|
|
pp = &(pcie->pp);
|
|
|
|
kirin_pcie_rd_own_conf(pp, PORT_MSI_CTRL_ADDR, 4, &val);
|
|
pcie->msi_controller_config[0] = val;
|
|
kirin_pcie_rd_own_conf(pp, PORT_MSI_CTRL_UPPER_ADDR, 4, &val);
|
|
pcie->msi_controller_config[1] = val;
|
|
kirin_pcie_rd_own_conf(pp, PORT_MSI_CTRL_INT0_ENABLE, 4, &val);
|
|
pcie->msi_controller_config[2] = val;
|
|
|
|
aer_pos = pci_find_ext_capability(pcie->rc_dev, PCI_EXT_CAP_ID_ERR);
|
|
if (!aer_pos ) {
|
|
dev_err(pcie->pp.dev, "Failed to get RC PCI_EXT_CAP_ID_ERR\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pci_read_config_dword(pcie->rc_dev, aer_pos + PCI_ERR_ROOT_COMMAND,
|
|
&pcie->aer_config);
|
|
|
|
ret = pci_save_state(pcie->rc_dev);
|
|
if (ret) {
|
|
dev_err(pcie->pp.dev, "Failed to save state of RC\n");
|
|
return -EINVAL;
|
|
}
|
|
pcie->rc_saved_state = pci_store_saved_state(pcie->rc_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int kirin_pcie_restore_rc_cfg(struct kirin_pcie *pcie)
|
|
{
|
|
struct pcie_port *pp;
|
|
int aer_pos;
|
|
|
|
pp = &(pcie->pp);
|
|
|
|
kirin_pcie_wr_own_conf(pp, PORT_MSI_CTRL_ADDR,
|
|
4, pcie->msi_controller_config[0]);
|
|
kirin_pcie_wr_own_conf(pp, PORT_MSI_CTRL_UPPER_ADDR,
|
|
4, pcie->msi_controller_config[1]);
|
|
kirin_pcie_wr_own_conf(pp, PORT_MSI_CTRL_INT0_ENABLE,
|
|
4, pcie->msi_controller_config[2]);
|
|
|
|
aer_pos = pci_find_ext_capability(pcie->rc_dev, PCI_EXT_CAP_ID_ERR);
|
|
if (!aer_pos ) {
|
|
dev_err(pcie->pp.dev, "Failed to get RC PCI_EXT_CAP_ID_ERR\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pci_write_config_dword(pcie->rc_dev, aer_pos + PCI_ERR_ROOT_COMMAND,
|
|
pcie->aer_config);
|
|
|
|
pci_load_saved_state(pcie->rc_dev, pcie->rc_saved_state);
|
|
pci_restore_state(pcie->rc_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int kirin_pcie_probe(struct platform_device *pdev)
|
|
{
|
|
struct kirin_pcie *pcie;
|
|
struct pcie_port *pp;
|
|
int ret;
|
|
u32 dev_id;
|
|
u32 vendor_id;
|
|
int (*pcie_probe)(struct kirin_pcie *);
|
|
|
|
if (!pdev->dev.of_node) {
|
|
dev_err(&pdev->dev, "NULL node\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pcie = devm_kzalloc(&pdev->dev,
|
|
sizeof(struct kirin_pcie), GFP_KERNEL);
|
|
if (!pcie)
|
|
return -ENOMEM;
|
|
|
|
pp = &pcie->pp;
|
|
g_kirin_pcie = pcie;
|
|
pp->dev = &pdev->dev;
|
|
|
|
pcie->pcie_ops= of_device_get_match_data(pp->dev);
|
|
if (!pcie->pcie_ops) {
|
|
dev_err(&pdev->dev, "failed to get pcie_ops\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pcie_probe = pcie->pcie_ops->pcie_probe;
|
|
if (!pcie_probe) {
|
|
dev_err(&pdev->dev, "failed to get pcie_probe function\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = pcie_probe(pcie);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "pcie_probe fail\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = kirin_add_pcie_port(pp, pdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
platform_set_drvdata(pdev, pcie);
|
|
|
|
kirin_pcie_rd_own_conf(pp, PCI_VENDOR_ID, 2, &vendor_id);
|
|
kirin_pcie_rd_own_conf(pp, PCI_DEVICE_ID, 2, &dev_id);
|
|
pcie->rc_dev = pci_get_device(vendor_id, dev_id, pcie->rc_dev);
|
|
if (!pcie->rc_dev) {
|
|
dev_err(&pdev->dev, "Failed to get RC device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = kirin_pcie_save_rc_cfg(pcie);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int kirin_pcie_resume_noirq(struct device *dev)
|
|
{
|
|
int ret;
|
|
struct kirin_pcie *pcie;
|
|
int (*pcie_resume_noirq)(struct device *);
|
|
|
|
pcie = dev_get_drvdata(dev);
|
|
if (!pcie) {
|
|
dev_err(dev, "Failed to get drvdata\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pcie_resume_noirq = pcie->pcie_ops->pcie_resume_noirq;
|
|
if (!pcie_resume_noirq) {
|
|
dev_err(dev, "failed to get pcie_resume_noirq function\n");
|
|
return 0;
|
|
}
|
|
|
|
ret = pcie_resume_noirq(dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = kirin_pcie_establish_link(&(pcie->pp));
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int kirin_pcie_suspend_noirq(struct device *dev)
|
|
{
|
|
int ret;
|
|
struct kirin_pcie *pcie;
|
|
int (*pcie_suspend_noirq)(struct device *);
|
|
|
|
pcie = dev_get_drvdata(dev);
|
|
if (!pcie) {
|
|
dev_err(dev, "Failed to get drvdata\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pcie_suspend_noirq = pcie->pcie_ops->pcie_suspend_noirq;
|
|
if (!pcie_suspend_noirq) {
|
|
dev_err(dev, "failed to get pcie_suspend_noirq function\n");
|
|
return 0;
|
|
}
|
|
|
|
ret = pcie_suspend_noirq(dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
|
|
#define kirin_pcie_suspend_noirq NULL
|
|
#define kirin_pcie_resume_noirq NULL
|
|
|
|
#endif
|
|
|
|
static const struct dev_pm_ops kirin_pcie_dev_pm_ops = {
|
|
.suspend_noirq = kirin_pcie_suspend_noirq,
|
|
.resume_noirq = kirin_pcie_resume_noirq,
|
|
};
|
|
|
|
|
|
static const struct of_device_id kirin_pcie_match[] = {
|
|
{
|
|
.compatible = "hisilicon,hikey960",
|
|
.data = &kirin960_pcie_ops
|
|
},
|
|
{
|
|
.compatible = "hisilicon,hikey970",
|
|
.data = &kirin970_pcie_ops
|
|
},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, kirin_pcie_match);
|
|
|
|
struct platform_driver kirin_pcie_driver = {
|
|
.probe = kirin_pcie_probe,
|
|
.driver = {
|
|
.name = "Kirin-pcie",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = kirin_pcie_match,
|
|
.pm =&kirin_pcie_dev_pm_ops,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(kirin_pcie_driver);
|