ae16f05c92
Introduce the advanced extended interrupt controllers (AVECINTC). This feature will allow each core to have 256 independent interrupt vectors and MSI interrupts can be independently routed to any vector on any CPU. The whole topology of irqchips in LoongArch machines looks like this if AVECINTC is supported: +-----+ +-----------------------+ +-------+ | IPI | --> | CPUINTC | <-- | Timer | +-----+ +-----------------------+ +-------+ ^ ^ ^ | | | +---------+ +----------+ +---------+ +-------+ | EIOINTC | | AVECINTC | | LIOINTC | <-- | UARTs | +---------+ +----------+ +---------+ +-------+ ^ ^ | | +---------+ +---------+ | PCH-PIC | | PCH-MSI | +---------+ +---------+ ^ ^ ^ | | | +---------+ +---------+ +---------+ | Devices | | PCH-LPC | | Devices | +---------+ +---------+ +---------+ ^ | +---------+ | Devices | +---------+ Co-developed-by: Jianmin Lv <lvjianmin@loongson.cn> Signed-off-by: Jianmin Lv <lvjianmin@loongson.cn> Co-developed-by: Liupu Wang <wangliupu@loongson.cn> Signed-off-by: Liupu Wang <wangliupu@loongson.cn> Co-developed-by: Huacai Chen <chenhuacai@loongson.cn> Signed-off-by: Huacai Chen <chenhuacai@loongson.cn> Signed-off-by: Tianyang Zhang <zhangtianyang@loongson.cn> Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Link: https://lore.kernel.org/all/20240823104337.25577-2-zhangtianyang@loongson.cn
296 lines
7.1 KiB
C
296 lines
7.1 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2020, Jiaxun Yang <jiaxun.yang@flygoat.com>
|
|
* Loongson PCH MSI support
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "pch-msi: " fmt
|
|
|
|
#include <linux/irqchip.h>
|
|
#include <linux/msi.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of_pci.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "irq-msi-lib.h"
|
|
#include "irq-loongson.h"
|
|
|
|
static int nr_pics;
|
|
|
|
struct pch_msi_data {
|
|
struct mutex msi_map_lock;
|
|
phys_addr_t doorbell;
|
|
u32 irq_first; /* The vector number that MSIs starts */
|
|
u32 num_irqs; /* The number of vectors for MSIs */
|
|
unsigned long *msi_map;
|
|
};
|
|
|
|
static struct fwnode_handle *pch_msi_handle[MAX_IO_PICS];
|
|
|
|
static int pch_msi_allocate_hwirq(struct pch_msi_data *priv, int num_req)
|
|
{
|
|
int first;
|
|
|
|
mutex_lock(&priv->msi_map_lock);
|
|
|
|
first = bitmap_find_free_region(priv->msi_map, priv->num_irqs,
|
|
get_count_order(num_req));
|
|
if (first < 0) {
|
|
mutex_unlock(&priv->msi_map_lock);
|
|
return -ENOSPC;
|
|
}
|
|
|
|
mutex_unlock(&priv->msi_map_lock);
|
|
|
|
return priv->irq_first + first;
|
|
}
|
|
|
|
static void pch_msi_free_hwirq(struct pch_msi_data *priv,
|
|
int hwirq, int num_req)
|
|
{
|
|
int first = hwirq - priv->irq_first;
|
|
|
|
mutex_lock(&priv->msi_map_lock);
|
|
bitmap_release_region(priv->msi_map, first, get_count_order(num_req));
|
|
mutex_unlock(&priv->msi_map_lock);
|
|
}
|
|
|
|
static void pch_msi_compose_msi_msg(struct irq_data *data,
|
|
struct msi_msg *msg)
|
|
{
|
|
struct pch_msi_data *priv = irq_data_get_irq_chip_data(data);
|
|
|
|
msg->address_hi = upper_32_bits(priv->doorbell);
|
|
msg->address_lo = lower_32_bits(priv->doorbell);
|
|
msg->data = data->hwirq;
|
|
}
|
|
|
|
static struct irq_chip middle_irq_chip = {
|
|
.name = "PCH MSI",
|
|
.irq_mask = irq_chip_mask_parent,
|
|
.irq_unmask = irq_chip_unmask_parent,
|
|
.irq_ack = irq_chip_ack_parent,
|
|
.irq_set_affinity = irq_chip_set_affinity_parent,
|
|
.irq_compose_msi_msg = pch_msi_compose_msi_msg,
|
|
};
|
|
|
|
static int pch_msi_parent_domain_alloc(struct irq_domain *domain,
|
|
unsigned int virq, int hwirq)
|
|
{
|
|
struct irq_fwspec fwspec;
|
|
|
|
fwspec.fwnode = domain->parent->fwnode;
|
|
fwspec.param_count = 1;
|
|
fwspec.param[0] = hwirq;
|
|
|
|
return irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
|
|
}
|
|
|
|
static int pch_msi_middle_domain_alloc(struct irq_domain *domain,
|
|
unsigned int virq,
|
|
unsigned int nr_irqs, void *args)
|
|
{
|
|
struct pch_msi_data *priv = domain->host_data;
|
|
int hwirq, err, i;
|
|
|
|
hwirq = pch_msi_allocate_hwirq(priv, nr_irqs);
|
|
if (hwirq < 0)
|
|
return hwirq;
|
|
|
|
for (i = 0; i < nr_irqs; i++) {
|
|
err = pch_msi_parent_domain_alloc(domain, virq + i, hwirq + i);
|
|
if (err)
|
|
goto err_hwirq;
|
|
|
|
irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
|
|
&middle_irq_chip, priv);
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_hwirq:
|
|
pch_msi_free_hwirq(priv, hwirq, nr_irqs);
|
|
irq_domain_free_irqs_parent(domain, virq, i);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void pch_msi_middle_domain_free(struct irq_domain *domain,
|
|
unsigned int virq,
|
|
unsigned int nr_irqs)
|
|
{
|
|
struct irq_data *d = irq_domain_get_irq_data(domain, virq);
|
|
struct pch_msi_data *priv = irq_data_get_irq_chip_data(d);
|
|
|
|
irq_domain_free_irqs_parent(domain, virq, nr_irqs);
|
|
pch_msi_free_hwirq(priv, d->hwirq, nr_irqs);
|
|
}
|
|
|
|
static const struct irq_domain_ops pch_msi_middle_domain_ops = {
|
|
.alloc = pch_msi_middle_domain_alloc,
|
|
.free = pch_msi_middle_domain_free,
|
|
.select = msi_lib_irq_domain_select,
|
|
};
|
|
|
|
#define PCH_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
|
|
MSI_FLAG_USE_DEF_CHIP_OPS | \
|
|
MSI_FLAG_PCI_MSI_MASK_PARENT)
|
|
|
|
#define PCH_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \
|
|
MSI_FLAG_PCI_MSIX | \
|
|
MSI_FLAG_MULTI_PCI_MSI)
|
|
|
|
static struct msi_parent_ops pch_msi_parent_ops = {
|
|
.required_flags = PCH_MSI_FLAGS_REQUIRED,
|
|
.supported_flags = PCH_MSI_FLAGS_SUPPORTED,
|
|
.bus_select_mask = MATCH_PCI_MSI,
|
|
.bus_select_token = DOMAIN_BUS_NEXUS,
|
|
.prefix = "PCH-",
|
|
.init_dev_msi_info = msi_lib_init_dev_msi_info,
|
|
};
|
|
|
|
static int pch_msi_init_domains(struct pch_msi_data *priv,
|
|
struct irq_domain *parent,
|
|
struct fwnode_handle *domain_handle)
|
|
{
|
|
struct irq_domain *middle_domain;
|
|
|
|
middle_domain = irq_domain_create_hierarchy(parent, 0, priv->num_irqs,
|
|
domain_handle,
|
|
&pch_msi_middle_domain_ops,
|
|
priv);
|
|
if (!middle_domain) {
|
|
pr_err("Failed to create the MSI middle domain\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
irq_domain_update_bus_token(middle_domain, DOMAIN_BUS_NEXUS);
|
|
|
|
middle_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
|
|
middle_domain->msi_parent_ops = &pch_msi_parent_ops;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pch_msi_init(phys_addr_t msg_address, int irq_base, int irq_count,
|
|
struct irq_domain *parent_domain, struct fwnode_handle *domain_handle)
|
|
{
|
|
int ret;
|
|
struct pch_msi_data *priv;
|
|
|
|
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
mutex_init(&priv->msi_map_lock);
|
|
|
|
priv->doorbell = msg_address;
|
|
priv->irq_first = irq_base;
|
|
priv->num_irqs = irq_count;
|
|
|
|
priv->msi_map = bitmap_zalloc(priv->num_irqs, GFP_KERNEL);
|
|
if (!priv->msi_map)
|
|
goto err_priv;
|
|
|
|
pr_debug("Registering %d MSIs, starting at %d\n",
|
|
priv->num_irqs, priv->irq_first);
|
|
|
|
ret = pch_msi_init_domains(priv, parent_domain, domain_handle);
|
|
if (ret)
|
|
goto err_map;
|
|
|
|
pch_msi_handle[nr_pics++] = domain_handle;
|
|
return 0;
|
|
|
|
err_map:
|
|
bitmap_free(priv->msi_map);
|
|
err_priv:
|
|
kfree(priv);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
static int pch_msi_of_init(struct device_node *node, struct device_node *parent)
|
|
{
|
|
int err;
|
|
int irq_base, irq_count;
|
|
struct resource res;
|
|
struct irq_domain *parent_domain;
|
|
|
|
parent_domain = irq_find_host(parent);
|
|
if (!parent_domain) {
|
|
pr_err("Failed to find the parent domain\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
if (of_address_to_resource(node, 0, &res)) {
|
|
pr_err("Failed to allocate resource\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (of_property_read_u32(node, "loongson,msi-base-vec", &irq_base)) {
|
|
pr_err("Unable to parse MSI vec base\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (of_property_read_u32(node, "loongson,msi-num-vecs", &irq_count)) {
|
|
pr_err("Unable to parse MSI vec number\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = pch_msi_init(res.start, irq_base, irq_count, parent_domain, of_node_to_fwnode(node));
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
IRQCHIP_DECLARE(pch_msi, "loongson,pch-msi-1.0", pch_msi_of_init);
|
|
#endif
|
|
|
|
#ifdef CONFIG_ACPI
|
|
struct fwnode_handle *get_pch_msi_handle(int pci_segment)
|
|
{
|
|
if (cpu_has_avecint)
|
|
return pch_msi_handle[0];
|
|
|
|
for (int i = 0; i < MAX_IO_PICS; i++) {
|
|
if (msi_group[i].pci_segment == pci_segment)
|
|
return pch_msi_handle[i];
|
|
}
|
|
return pch_msi_handle[0];
|
|
}
|
|
|
|
int __init pch_msi_acpi_init(struct irq_domain *parent, struct acpi_madt_msi_pic *acpi_pchmsi)
|
|
{
|
|
int ret;
|
|
struct fwnode_handle *domain_handle;
|
|
|
|
domain_handle = irq_domain_alloc_fwnode(&acpi_pchmsi->msg_address);
|
|
ret = pch_msi_init(acpi_pchmsi->msg_address, acpi_pchmsi->start,
|
|
acpi_pchmsi->count, parent, domain_handle);
|
|
if (ret < 0)
|
|
irq_domain_free_fwnode(domain_handle);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int __init pch_msi_acpi_init_avec(struct irq_domain *parent)
|
|
{
|
|
if (pch_msi_handle[0])
|
|
return 0;
|
|
|
|
pch_msi_handle[0] = parent->fwnode;
|
|
irq_domain_update_bus_token(parent, DOMAIN_BUS_NEXUS);
|
|
|
|
parent->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
|
|
parent->msi_parent_ops = &pch_msi_parent_ops;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|