// SPDX-License-Identifier: GPL-2.0 /* * PCIe host controller driver for AMD MDB PCIe Bridge * * Copyright (C) 2024-2025, Advanced Micro Devices, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "pcie-designware.h" #define AMD_MDB_TLP_IR_STATUS_MISC 0x4C0 #define AMD_MDB_TLP_IR_MASK_MISC 0x4C4 #define AMD_MDB_TLP_IR_ENABLE_MISC 0x4C8 #define AMD_MDB_TLP_IR_DISABLE_MISC 0x4CC #define AMD_MDB_TLP_PCIE_INTX_MASK GENMASK(23, 16) #define AMD_MDB_PCIE_INTR_INTX_ASSERT(x) BIT((x) * 2) /* Interrupt registers definitions. */ #define AMD_MDB_PCIE_INTR_CMPL_TIMEOUT 15 #define AMD_MDB_PCIE_INTR_INTX 16 #define AMD_MDB_PCIE_INTR_PM_PME_RCVD 24 #define AMD_MDB_PCIE_INTR_PME_TO_ACK_RCVD 25 #define AMD_MDB_PCIE_INTR_MISC_CORRECTABLE 26 #define AMD_MDB_PCIE_INTR_NONFATAL 27 #define AMD_MDB_PCIE_INTR_FATAL 28 #define IMR(x) BIT(AMD_MDB_PCIE_INTR_ ##x) #define AMD_MDB_PCIE_IMR_ALL_MASK \ ( \ IMR(CMPL_TIMEOUT) | \ IMR(PM_PME_RCVD) | \ IMR(PME_TO_ACK_RCVD) | \ IMR(MISC_CORRECTABLE) | \ IMR(NONFATAL) | \ IMR(FATAL) | \ AMD_MDB_TLP_PCIE_INTX_MASK \ ) /** * struct amd_mdb_pcie - PCIe port information * @pci: DesignWare PCIe controller structure * @slcr: MDB System Level Control and Status Register (SLCR) base * @intx_domain: INTx IRQ domain pointer * @mdb_domain: MDB IRQ domain pointer * @intx_irq: INTx IRQ interrupt number */ struct amd_mdb_pcie { struct dw_pcie pci; void __iomem *slcr; struct irq_domain *intx_domain; struct irq_domain *mdb_domain; int intx_irq; }; static const struct dw_pcie_host_ops amd_mdb_pcie_host_ops = { }; static void amd_mdb_intx_irq_mask(struct irq_data *data) { struct amd_mdb_pcie *pcie = irq_data_get_irq_chip_data(data); struct dw_pcie *pci = &pcie->pci; struct dw_pcie_rp *port = &pci->pp; unsigned long flags; u32 val; raw_spin_lock_irqsave(&port->lock, flags); val = FIELD_PREP(AMD_MDB_TLP_PCIE_INTX_MASK, AMD_MDB_PCIE_INTR_INTX_ASSERT(data->hwirq)); /* * Writing '1' to a bit in AMD_MDB_TLP_IR_DISABLE_MISC disables that * interrupt, writing '0' has no effect. */ writel_relaxed(val, pcie->slcr + AMD_MDB_TLP_IR_DISABLE_MISC); raw_spin_unlock_irqrestore(&port->lock, flags); } static void amd_mdb_intx_irq_unmask(struct irq_data *data) { struct amd_mdb_pcie *pcie = irq_data_get_irq_chip_data(data); struct dw_pcie *pci = &pcie->pci; struct dw_pcie_rp *port = &pci->pp; unsigned long flags; u32 val; raw_spin_lock_irqsave(&port->lock, flags); val = FIELD_PREP(AMD_MDB_TLP_PCIE_INTX_MASK, AMD_MDB_PCIE_INTR_INTX_ASSERT(data->hwirq)); /* * Writing '1' to a bit in AMD_MDB_TLP_IR_ENABLE_MISC enables that * interrupt, writing '0' has no effect. */ writel_relaxed(val, pcie->slcr + AMD_MDB_TLP_IR_ENABLE_MISC); raw_spin_unlock_irqrestore(&port->lock, flags); } static struct irq_chip amd_mdb_intx_irq_chip = { .name = "AMD MDB INTx", .irq_mask = amd_mdb_intx_irq_mask, .irq_unmask = amd_mdb_intx_irq_unmask, }; /** * amd_mdb_pcie_intx_map - Set the handler for the INTx and mark IRQ as valid * @domain: IRQ domain * @irq: Virtual IRQ number * @hwirq: Hardware interrupt number * * Return: Always returns '0'. */ static int amd_mdb_pcie_intx_map(struct irq_domain *domain, unsigned int irq, irq_hw_number_t hwirq) { irq_set_chip_and_handler(irq, &amd_mdb_intx_irq_chip, handle_level_irq); irq_set_chip_data(irq, domain->host_data); irq_set_status_flags(irq, IRQ_LEVEL); return 0; } /* INTx IRQ domain operations. */ static const struct irq_domain_ops amd_intx_domain_ops = { .map = amd_mdb_pcie_intx_map, }; static irqreturn_t dw_pcie_rp_intx(int irq, void *args) { struct amd_mdb_pcie *pcie = args; unsigned long val; int i, int_status; val = readl_relaxed(pcie->slcr + AMD_MDB_TLP_IR_STATUS_MISC); int_status = FIELD_GET(AMD_MDB_TLP_PCIE_INTX_MASK, val); for (i = 0; i < PCI_NUM_INTX; i++) { if (int_status & AMD_MDB_PCIE_INTR_INTX_ASSERT(i)) generic_handle_domain_irq(pcie->intx_domain, i); } return IRQ_HANDLED; } #define _IC(x, s)[AMD_MDB_PCIE_INTR_ ## x] = { __stringify(x), s } static const struct { const char *sym; const char *str; } intr_cause[32] = { _IC(CMPL_TIMEOUT, "Completion timeout"), _IC(PM_PME_RCVD, "PM_PME message received"), _IC(PME_TO_ACK_RCVD, "PME_TO_ACK message received"), _IC(MISC_CORRECTABLE, "Correctable error message"), _IC(NONFATAL, "Non fatal error message"), _IC(FATAL, "Fatal error message"), }; static void amd_mdb_event_irq_mask(struct irq_data *d) { struct amd_mdb_pcie *pcie = irq_data_get_irq_chip_data(d); struct dw_pcie *pci = &pcie->pci; struct dw_pcie_rp *port = &pci->pp; unsigned long flags; u32 val; raw_spin_lock_irqsave(&port->lock, flags); val = BIT(d->hwirq); writel_relaxed(val, pcie->slcr + AMD_MDB_TLP_IR_DISABLE_MISC); raw_spin_unlock_irqrestore(&port->lock, flags); } static void amd_mdb_event_irq_unmask(struct irq_data *d) { struct amd_mdb_pcie *pcie = irq_data_get_irq_chip_data(d); struct dw_pcie *pci = &pcie->pci; struct dw_pcie_rp *port = &pci->pp; unsigned long flags; u32 val; raw_spin_lock_irqsave(&port->lock, flags); val = BIT(d->hwirq); writel_relaxed(val, pcie->slcr + AMD_MDB_TLP_IR_ENABLE_MISC); raw_spin_unlock_irqrestore(&port->lock, flags); } static struct irq_chip amd_mdb_event_irq_chip = { .name = "AMD MDB RC-Event", .irq_mask = amd_mdb_event_irq_mask, .irq_unmask = amd_mdb_event_irq_unmask, }; static int amd_mdb_pcie_event_map(struct irq_domain *domain, unsigned int irq, irq_hw_number_t hwirq) { irq_set_chip_and_handler(irq, &amd_mdb_event_irq_chip, handle_level_irq); irq_set_chip_data(irq, domain->host_data); irq_set_status_flags(irq, IRQ_LEVEL); return 0; } static const struct irq_domain_ops event_domain_ops = { .map = amd_mdb_pcie_event_map, }; static irqreturn_t amd_mdb_pcie_event(int irq, void *args) { struct amd_mdb_pcie *pcie = args; unsigned long val; int i; val = readl_relaxed(pcie->slcr + AMD_MDB_TLP_IR_STATUS_MISC); val &= ~readl_relaxed(pcie->slcr + AMD_MDB_TLP_IR_MASK_MISC); for_each_set_bit(i, &val, 32) generic_handle_domain_irq(pcie->mdb_domain, i); writel_relaxed(val, pcie->slcr + AMD_MDB_TLP_IR_STATUS_MISC); return IRQ_HANDLED; } static void amd_mdb_pcie_free_irq_domains(struct amd_mdb_pcie *pcie) { if (pcie->intx_domain) { irq_domain_remove(pcie->intx_domain); pcie->intx_domain = NULL; } if (pcie->mdb_domain) { irq_domain_remove(pcie->mdb_domain); pcie->mdb_domain = NULL; } } static int amd_mdb_pcie_init_port(struct amd_mdb_pcie *pcie) { unsigned long val; /* Disable all TLP interrupts. */ writel_relaxed(AMD_MDB_PCIE_IMR_ALL_MASK, pcie->slcr + AMD_MDB_TLP_IR_DISABLE_MISC); /* Clear pending TLP interrupts. */ val = readl_relaxed(pcie->slcr + AMD_MDB_TLP_IR_STATUS_MISC); val &= AMD_MDB_PCIE_IMR_ALL_MASK; writel_relaxed(val, pcie->slcr + AMD_MDB_TLP_IR_STATUS_MISC); /* Enable all TLP interrupts. */ writel_relaxed(AMD_MDB_PCIE_IMR_ALL_MASK, pcie->slcr + AMD_MDB_TLP_IR_ENABLE_MISC); return 0; } /** * amd_mdb_pcie_init_irq_domains - Initialize IRQ domain * @pcie: PCIe port information * @pdev: Platform device * * Return: Returns '0' on success and error value on failure. */ static int amd_mdb_pcie_init_irq_domains(struct amd_mdb_pcie *pcie, struct platform_device *pdev) { struct dw_pcie *pci = &pcie->pci; struct dw_pcie_rp *pp = &pci->pp; struct device *dev = &pdev->dev; struct device_node *node = dev->of_node; struct device_node *pcie_intc_node; int err; pcie_intc_node = of_get_next_child(node, NULL); if (!pcie_intc_node) { dev_err(dev, "No PCIe Intc node found\n"); return -ENODEV; } pcie->mdb_domain = irq_domain_add_linear(pcie_intc_node, 32, &event_domain_ops, pcie); if (!pcie->mdb_domain) { err = -ENOMEM; dev_err(dev, "Failed to add MDB domain\n"); goto out; } irq_domain_update_bus_token(pcie->mdb_domain, DOMAIN_BUS_NEXUS); pcie->intx_domain = irq_domain_add_linear(pcie_intc_node, PCI_NUM_INTX, &amd_intx_domain_ops, pcie); if (!pcie->intx_domain) { err = -ENOMEM; dev_err(dev, "Failed to add INTx domain\n"); goto mdb_out; } of_node_put(pcie_intc_node); irq_domain_update_bus_token(pcie->intx_domain, DOMAIN_BUS_WIRED); raw_spin_lock_init(&pp->lock); return 0; mdb_out: amd_mdb_pcie_free_irq_domains(pcie); out: of_node_put(pcie_intc_node); return err; } static irqreturn_t amd_mdb_pcie_intr_handler(int irq, void *args) { struct amd_mdb_pcie *pcie = args; struct device *dev; struct irq_data *d; dev = pcie->pci.dev; /* * In the future, error reporting will be hooked to the AER subsystem. * Currently, the driver prints a warning message to the user. */ d = irq_domain_get_irq_data(pcie->mdb_domain, irq); if (intr_cause[d->hwirq].str) dev_warn(dev, "%s\n", intr_cause[d->hwirq].str); else dev_warn_once(dev, "Unknown IRQ %ld\n", d->hwirq); return IRQ_HANDLED; } static int amd_mdb_setup_irq(struct amd_mdb_pcie *pcie, struct platform_device *pdev) { struct dw_pcie *pci = &pcie->pci; struct dw_pcie_rp *pp = &pci->pp; struct device *dev = &pdev->dev; int i, irq, err; amd_mdb_pcie_init_port(pcie); pp->irq = platform_get_irq(pdev, 0); if (pp->irq < 0) return pp->irq; for (i = 0; i < ARRAY_SIZE(intr_cause); i++) { if (!intr_cause[i].str) continue; irq = irq_create_mapping(pcie->mdb_domain, i); if (!irq) { dev_err(dev, "Failed to map MDB domain interrupt\n"); return -ENOMEM; } err = devm_request_irq(dev, irq, amd_mdb_pcie_intr_handler, IRQF_NO_THREAD, intr_cause[i].sym, pcie); if (err) { dev_err(dev, "Failed to request IRQ %d, err=%d\n", irq, err); return err; } } pcie->intx_irq = irq_create_mapping(pcie->mdb_domain, AMD_MDB_PCIE_INTR_INTX); if (!pcie->intx_irq) { dev_err(dev, "Failed to map INTx interrupt\n"); return -ENXIO; } err = devm_request_irq(dev, pcie->intx_irq, dw_pcie_rp_intx, IRQF_NO_THREAD, NULL, pcie); if (err) { dev_err(dev, "Failed to request INTx IRQ %d, err=%d\n", irq, err); return err; } /* Plug the main event handler. */ err = devm_request_irq(dev, pp->irq, amd_mdb_pcie_event, IRQF_NO_THREAD, "amd_mdb pcie_irq", pcie); if (err) { dev_err(dev, "Failed to request event IRQ %d, err=%d\n", pp->irq, err); return err; } return 0; } static int amd_mdb_add_pcie_port(struct amd_mdb_pcie *pcie, struct platform_device *pdev) { struct dw_pcie *pci = &pcie->pci; struct dw_pcie_rp *pp = &pci->pp; struct device *dev = &pdev->dev; int err; pcie->slcr = devm_platform_ioremap_resource_byname(pdev, "slcr"); if (IS_ERR(pcie->slcr)) return PTR_ERR(pcie->slcr); err = amd_mdb_pcie_init_irq_domains(pcie, pdev); if (err) return err; err = amd_mdb_setup_irq(pcie, pdev); if (err) { dev_err(dev, "Failed to set up interrupts, err=%d\n", err); goto out; } pp->ops = &amd_mdb_pcie_host_ops; err = dw_pcie_host_init(pp); if (err) { dev_err(dev, "Failed to initialize host, err=%d\n", err); goto out; } return 0; out: amd_mdb_pcie_free_irq_domains(pcie); return err; } static int amd_mdb_pcie_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct amd_mdb_pcie *pcie; struct dw_pcie *pci; pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL); if (!pcie) return -ENOMEM; pci = &pcie->pci; pci->dev = dev; platform_set_drvdata(pdev, pcie); return amd_mdb_add_pcie_port(pcie, pdev); } static const struct of_device_id amd_mdb_pcie_of_match[] = { { .compatible = "amd,versal2-mdb-host", }, {}, }; static struct platform_driver amd_mdb_pcie_driver = { .driver = { .name = "amd-mdb-pcie", .of_match_table = amd_mdb_pcie_of_match, .suppress_bind_attrs = true, }, .probe = amd_mdb_pcie_probe, }; builtin_platform_driver(amd_mdb_pcie_driver);