Commit c69bb91c authored by Mark Brown's avatar Mark Brown

regmap IRQ support for devices with multiple IRQs

Merge series from Matti Vaittinen <mazziesaccount@gmail.com>:

Devices can provide multiple interrupt lines. One reason for this is that
a device has multiple subfunctions, each providing its own interrupt line.
Another reason is that a device can be designed to be used (also) on a
system where some of the interrupts can be routed to another processor.

A line often further acts as a demultiplex for specific interrupts
and has it's respective set of interrupt (status, mask, ack, ...)
registers.

Regmap supports the handling of these registers and demultiplexing
interrupts, but interrupt domain code ends up assigning the same name for
the per interrupt line domains

This series adds possibility for giving a name suffix for an interrupt

Previous discussion can be found from:
https://lore.kernel.org/all/87plst28yk.ffs@tglx/
https://lore.kernel.org/all/15685ef6-92a5-41df-9148-1a67ceaec47b@gmail.com/

The domain suffix support added in this series will be used by the
ROHM BD96801 ERRB IRQ support code. The BD96801 ERRB support will need
the initial BD96801 driver code, which is not yet in irq/core or regmap
trees. Thus the user for this new support is not included in the series,
but will be sent once the name suffix support gets merged.
parents 7c626ce4 dde286ee
......@@ -608,6 +608,30 @@ int regmap_irq_set_type_config_simple(unsigned int **buf, unsigned int type,
}
EXPORT_SYMBOL_GPL(regmap_irq_set_type_config_simple);
static int regmap_irq_create_domain(struct fwnode_handle *fwnode, int irq_base,
const struct regmap_irq_chip *chip,
struct regmap_irq_chip_data *d)
{
struct irq_domain_info info = {
.fwnode = fwnode,
.size = chip->num_irqs,
.hwirq_max = chip->num_irqs,
.virq_base = irq_base,
.ops = &regmap_domain_ops,
.host_data = d,
.name_suffix = chip->domain_suffix,
};
d->domain = irq_domain_instantiate(&info);
if (IS_ERR(d->domain)) {
dev_err(d->map->dev, "Failed to create IRQ domain\n");
return PTR_ERR(d->domain);
}
return 0;
}
/**
* regmap_add_irq_chip_fwnode() - Use standard regmap IRQ controller handling
*
......@@ -856,18 +880,9 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode,
}
}
if (irq_base)
d->domain = irq_domain_create_legacy(fwnode, chip->num_irqs,
irq_base, 0,
&regmap_domain_ops, d);
else
d->domain = irq_domain_create_linear(fwnode, chip->num_irqs,
&regmap_domain_ops, d);
if (!d->domain) {
dev_err(map->dev, "Failed to create IRQ domain\n");
ret = -ENOMEM;
ret = regmap_irq_create_domain(fwnode, irq_base, chip, d);
if (ret)
goto err_alloc;
}
ret = request_threaded_irq(irq, NULL, regmap_irq_thread,
irq_flags | IRQF_ONESHOT,
......
......@@ -291,7 +291,12 @@ struct irq_domain_chip_generic_info;
* @hwirq_max: Maximum number of interrupts supported by controller
* @direct_max: Maximum value of direct maps;
* Use ~0 for no limit; 0 for no direct mapping
* @hwirq_base: The first hardware interrupt number (legacy domains only)
* @virq_base: The first Linux interrupt number for legacy domains to
* immediately associate the interrupts after domain creation
* @bus_token: Domain bus token
* @name_suffix: Optional name suffix to avoid collisions when multiple
* domains are added using same fwnode
* @ops: Domain operation callbacks
* @host_data: Controller private data pointer
* @dgc_info: Geneneric chip information structure pointer used to
......@@ -307,7 +312,10 @@ struct irq_domain_info {
unsigned int size;
irq_hw_number_t hwirq_max;
int direct_max;
unsigned int hwirq_base;
unsigned int virq_base;
enum irq_domain_bus_token bus_token;
const char *name_suffix;
const struct irq_domain_ops *ops;
void *host_data;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
......
......@@ -1521,6 +1521,9 @@ struct regmap_irq_chip_data;
* struct regmap_irq_chip - Description of a generic regmap irq_chip.
*
* @name: Descriptive name for IRQ controller.
* @domain_suffix: Name suffix to be appended to end of IRQ domain name. Needed
* when multiple regmap-IRQ controllers are created from same
* device.
*
* @main_status: Base main status register address. For chips which have
* interrupts arranged in separate sub-irq blocks with own IRQ
......@@ -1606,6 +1609,7 @@ struct regmap_irq_chip_data;
*/
struct regmap_irq_chip {
const char *name;
const char *domain_suffix;
unsigned int main_status;
unsigned int num_main_status_bits;
......
......@@ -128,72 +128,92 @@ void irq_domain_free_fwnode(struct fwnode_handle *fwnode)
}
EXPORT_SYMBOL_GPL(irq_domain_free_fwnode);
static int irq_domain_set_name(struct irq_domain *domain,
const struct fwnode_handle *fwnode,
enum irq_domain_bus_token bus_token)
static int alloc_name(struct irq_domain *domain, char *base, enum irq_domain_bus_token bus_token)
{
static atomic_t unknown_domains;
struct irqchip_fwid *fwid;
if (is_fwnode_irqchip(fwnode)) {
fwid = container_of(fwnode, struct irqchip_fwid, fwnode);
switch (fwid->type) {
case IRQCHIP_FWNODE_NAMED:
case IRQCHIP_FWNODE_NAMED_ID:
domain->name = bus_token ?
kasprintf(GFP_KERNEL, "%s-%d",
fwid->name, bus_token) :
kstrdup(fwid->name, GFP_KERNEL);
if (!domain->name)
return -ENOMEM;
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
break;
default:
domain->name = fwid->name;
if (bus_token) {
domain->name = kasprintf(GFP_KERNEL, "%s-%d",
fwid->name, bus_token);
domain->name = bus_token ? kasprintf(GFP_KERNEL, "%s-%d", base, bus_token) :
kasprintf(GFP_KERNEL, "%s", base);
if (!domain->name)
return -ENOMEM;
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
}
break;
}
} else if (is_of_node(fwnode) || is_acpi_device_node(fwnode) ||
is_software_node(fwnode)) {
return 0;
}
static int alloc_fwnode_name(struct irq_domain *domain, const struct fwnode_handle *fwnode,
enum irq_domain_bus_token bus_token, const char *suffix)
{
const char *sep = suffix ? "-" : "";
const char *suf = suffix ? : "";
char *name;
/*
* fwnode paths contain '/', which debugfs is legitimately
* unhappy about. Replace them with ':', which does
* the trick and is not as offensive as '\'...
*/
name = bus_token ?
kasprintf(GFP_KERNEL, "%pfw-%d", fwnode, bus_token) :
kasprintf(GFP_KERNEL, "%pfw", fwnode);
name = bus_token ? kasprintf(GFP_KERNEL, "%pfw-%s%s%d", fwnode, suf, sep, bus_token) :
kasprintf(GFP_KERNEL, "%pfw-%s", fwnode, suf);
if (!name)
return -ENOMEM;
/*
* fwnode paths contain '/', which debugfs is legitimately unhappy
* about. Replace them with ':', which does the trick and is not as
* offensive as '\'...
*/
domain->name = strreplace(name, '/', ':');
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
}
return 0;
}
static int alloc_unknown_name(struct irq_domain *domain, enum irq_domain_bus_token bus_token)
{
static atomic_t unknown_domains;
int id = atomic_inc_return(&unknown_domains);
domain->name = bus_token ? kasprintf(GFP_KERNEL, "unknown-%d-%d", id, bus_token) :
kasprintf(GFP_KERNEL, "unknown-%d", id);
if (!domain->name) {
if (fwnode)
pr_err("Invalid fwnode type for irqdomain\n");
domain->name = bus_token ?
kasprintf(GFP_KERNEL, "unknown-%d-%d",
atomic_inc_return(&unknown_domains),
bus_token) :
kasprintf(GFP_KERNEL, "unknown-%d",
atomic_inc_return(&unknown_domains));
if (!domain->name)
return -ENOMEM;
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
return 0;
}
static int irq_domain_set_name(struct irq_domain *domain, const struct irq_domain_info *info)
{
enum irq_domain_bus_token bus_token = info->bus_token;
const struct fwnode_handle *fwnode = info->fwnode;
if (is_fwnode_irqchip(fwnode)) {
struct irqchip_fwid *fwid = container_of(fwnode, struct irqchip_fwid, fwnode);
/*
* The name_suffix is only intended to be used to avoid a name
* collision when multiple domains are created for a single
* device and the name is picked using a real device node.
* (Typical use-case is regmap-IRQ controllers for devices
* providing more than one physical IRQ.) There should be no
* need to use name_suffix with irqchip-fwnode.
*/
if (info->name_suffix)
return -EINVAL;
switch (fwid->type) {
case IRQCHIP_FWNODE_NAMED:
case IRQCHIP_FWNODE_NAMED_ID:
return alloc_name(domain, fwid->name, bus_token);
default:
domain->name = fwid->name;
if (bus_token)
return alloc_name(domain, fwid->name, bus_token);
}
} else if (is_of_node(fwnode) || is_acpi_device_node(fwnode) || is_software_node(fwnode)) {
return alloc_fwnode_name(domain, fwnode, bus_token, info->name_suffix);
}
if (domain->name)
return 0;
if (fwnode)
pr_err("Invalid fwnode type for irqdomain\n");
return alloc_unknown_name(domain, bus_token);
}
static struct irq_domain *__irq_domain_create(const struct irq_domain_info *info)
......@@ -211,7 +231,7 @@ static struct irq_domain *__irq_domain_create(const struct irq_domain_info *info
if (!domain)
return ERR_PTR(-ENOMEM);
err = irq_domain_set_name(domain, info->fwnode, info->bus_token);
err = irq_domain_set_name(domain, info);
if (err) {
kfree(domain);
return ERR_PTR(err);
......@@ -267,13 +287,20 @@ static void irq_domain_free(struct irq_domain *domain)
kfree(domain);
}
/**
* irq_domain_instantiate() - Instantiate a new irq domain data structure
* @info: Domain information pointer pointing to the information for this domain
*
* Return: A pointer to the instantiated irq domain or an ERR_PTR value.
*/
struct irq_domain *irq_domain_instantiate(const struct irq_domain_info *info)
static void irq_domain_instantiate_descs(const struct irq_domain_info *info)
{
if (!IS_ENABLED(CONFIG_SPARSE_IRQ))
return;
if (irq_alloc_descs(info->virq_base, info->virq_base, info->size,
of_node_to_nid(to_of_node(info->fwnode))) < 0) {
pr_info("Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",
info->virq_base);
}
}
static struct irq_domain *__irq_domain_instantiate(const struct irq_domain_info *info,
bool cond_alloc_descs)
{
struct irq_domain *domain;
int err;
......@@ -306,6 +333,15 @@ struct irq_domain *irq_domain_instantiate(const struct irq_domain_info *info)
__irq_domain_publish(domain);
if (cond_alloc_descs && info->virq_base > 0)
irq_domain_instantiate_descs(info);
/* Legacy interrupt domains have a fixed Linux interrupt number */
if (info->virq_base > 0) {
irq_domain_associate_many(domain, info->virq_base, info->hwirq_base,
info->size - info->hwirq_base);
}
return domain;
err_domain_gc_remove:
......@@ -315,6 +351,17 @@ struct irq_domain *irq_domain_instantiate(const struct irq_domain_info *info)
irq_domain_free(domain);
return ERR_PTR(err);
}
/**
* irq_domain_instantiate() - Instantiate a new irq domain data structure
* @info: Domain information pointer pointing to the information for this domain
*
* Return: A pointer to the instantiated irq domain or an ERR_PTR value.
*/
struct irq_domain *irq_domain_instantiate(const struct irq_domain_info *info)
{
return __irq_domain_instantiate(info, false);
}
EXPORT_SYMBOL_GPL(irq_domain_instantiate);
/**
......@@ -413,28 +460,13 @@ struct irq_domain *irq_domain_create_simple(struct fwnode_handle *fwnode,
.fwnode = fwnode,
.size = size,
.hwirq_max = size,
.virq_base = first_irq,
.ops = ops,
.host_data = host_data,
};
struct irq_domain *domain;
struct irq_domain *domain = __irq_domain_instantiate(&info, true);
domain = irq_domain_instantiate(&info);
if (IS_ERR(domain))
return NULL;
if (first_irq > 0) {
if (IS_ENABLED(CONFIG_SPARSE_IRQ)) {
/* attempt to allocated irq_descs */
int rc = irq_alloc_descs(first_irq, first_irq, size,
of_node_to_nid(to_of_node(fwnode)));
if (rc < 0)
pr_info("Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",
first_irq);
}
irq_domain_associate_many(domain, first_irq, 0, size);
}
return domain;
return IS_ERR(domain) ? NULL : domain;
}
EXPORT_SYMBOL_GPL(irq_domain_create_simple);
......@@ -476,18 +508,14 @@ struct irq_domain *irq_domain_create_legacy(struct fwnode_handle *fwnode,
.fwnode = fwnode,
.size = first_hwirq + size,
.hwirq_max = first_hwirq + size,
.hwirq_base = first_hwirq,
.virq_base = first_irq,
.ops = ops,
.host_data = host_data,
};
struct irq_domain *domain;
domain = irq_domain_instantiate(&info);
if (IS_ERR(domain))
return NULL;
irq_domain_associate_many(domain, first_irq, first_hwirq, size);
struct irq_domain *domain = irq_domain_instantiate(&info);
return domain;
return IS_ERR(domain) ? NULL : domain;
}
EXPORT_SYMBOL_GPL(irq_domain_create_legacy);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment