Re: [PATCH v2 4/6] irqchip/irq-pruss-intc: Add helper functions to configure internal mapping

From: Suman Anna
Date: Mon Aug 12 2019 - 15:40:22 EST


Hi David,

On 8/8/19 12:09 PM, David Lechner wrote:
> On 8/2/19 4:26 PM, Suman Anna wrote:
>> Point is different applications might use mapping differently as per
>> their firmware and driver/application design and their split across one
>> or more PRUs (design by contract). And we need to set this up at runtime
>> when the application driver is getting run. We will have either the Soft
>> UART or the Ethernet running at a time depending on the end goal desired
>>
>>> I have an idea that we can use multiple struct irq_domains to make
>>> this work in the existing IRQ framework, but it would be helpful to
>>> know more about the bigger picture first.
>>
>> Yeah, would be great if there is a way this can be solved without having
>> to introduce additional API.
>>
>
>
> Here is what I came up with to use existing IRQ APIs to implement event
> mapping.
> Basically it is the same as my previous suggestion [1], with the
> addition of
> multiple IRQ domains.

First of all, many thanks for looking into the problem and providing
patches for the alternate solutions. If we were to not use any exported
functions, this approach does seem to be a viable solution. I am going
to play around with both [1] and this patch with all our existing
usecases and see if I run into any issues.

So, w.r.t this patch compared to [1], is the multiple IRQ domain solving
anything specifically? Our main issue is the re-purposing of a event
(and its mapping depending on the application), and the same issue will
remain whether we have multiple domains or not. Also, now we would
expect an event to migrate between different domains based on its usage.

>
> The idea is that each external interrupt controller (or DMA controller,
> etc.)
> that is connected to the PRUSS interrupt controller is considered an
> interrupt
> domain. One of the objections to my previous patch was that we could
> only have
> one IRQ descriptor per event. Now we can have one descriptor per event per
> domain.
>
> I am still proposing that we use the interrupt-cells and identical vendor
> resource data structures in the PRU firmware be used to provide the mapping
> information. (As a side note, I still think it is important to include
> EVTSEL
> on AM18xx in order to fully describe the event.)

W.r.t EVTSEL, it is a global value and applies to a range of events. I
have another equivalent register/functionality on most of the other SoCs
as well (a register in PRUSS_CFG space) that muxes standard events vs
MII_RT events. Again, that is limited to only a subset of all the system
events. So, should this continue to be a per event specifier, it will be
yet another mapping configuration data item (my idea was to manage this
once per application within the PRU remoteproc driver along with the
fwspec mapping).

regards
Suman

>
> The bindings will have N = 4 cells (or N = 5 when EVTSEL is required to
> fully
> describe the event):
>
> ÂÂÂÂCell 0: The PRUSS event number, e.g. 0 to 64 for most PRUSSs
> ÂÂÂÂCell 1: The EVTSEL value (omitted when N == 4), e.g. 0, 1 or
> ÂÂÂÂÂÂÂ TI_PRUSS_INTC_EVTSEL_ANY if the event is the same for all EVTSEL
> ÂÂÂÂÂÂÂ values. On AM18xx, external events will all require 0 or 1 while
> ÂÂÂÂÂÂÂ system events will always be TI_PRUSS_INTC_EVTSEL_ANY.
> ÂÂÂÂCell N-3: The channel that the event gets mapped to, e.g. 0 to 9
> ÂÂÂÂCell N-2: The host that the channel gets mapped to, e.g. 0 to 9
> ÂÂÂÂCell N-1: The interrupt domain, e.g. TI_PRUSS_INTC_DOMAIN_PRU or
> ÂÂÂÂÂÂÂ TI_PRUSS_INTC_DOMAIN_MCU
>
> The TI_PRUSS_INTC_DOMAIN_* values are just arbitrary numbers assigned to
> the
> possible domains. For example, on AM18xx and AM33xx, there are just two
> domains,
> the PRU domain for host 0 and host 1 and the MCU domain for host 2 thru 9.
> Looking at the AM65xx manual, it looks like it would have 4 domains, the
> PRU
> domain, the RTU PRU domain, the MCU domain and a task manager domain.
> (And I
> suppose that domains could even be more granular if needed, e.g. we
> could drop
> the arbitrary domain number and treat each host interrupt/event as an
> interrupt
> domain, then there would be an IRQ descriptor per PRU INTC event per host.)
>
> The AM18xx example I have been using will look like this in the device
> tree:
>
> ÂÂÂÂinterrupts = <63 TI_PRUSS_INTC_EVTSEL_ANY 0 0
> TI_PRUSS_INTC_DOMAIN_PRU>,
> ÂÂÂÂÂÂÂÂÂÂÂÂ <62 TI_PRUSS_INTC_EVTSEL_ANY 2 2 TI_PRUSS_INTC_DOMAIN_MCU>;
>
> To keep parsing simple, the PRU firmware can include vendor resources
> that have
> essentially the same format as the device tree bindings. For example:
>
> enum {
> ÂÂÂÂ/* IRQ descriptor without EVTSEL */
> ÂÂÂÂTI_PRU_VENDOR_RESOURCE_IRQ = RSC_VENDOR_START,
> ÂÂÂÂ/* IRQ descriptor with EVTSEL */
> ÂÂÂÂTI_PRU_VENDOR_RESOURCE_IRQ2,
> };
>
> struct ti_pru_vendor_resource_irq {
> ÂÂÂÂ__le32 event;
> ÂÂÂÂ__le32 channel;
> ÂÂÂÂ__le32 host;
> ÂÂÂÂ__le32 domain;
> };
>
> struct ti_pru_vendor_resource_irq2 {
> ÂÂÂÂ__le32 event;
> ÂÂÂÂ__le32 evt_sel;
> ÂÂÂÂ__le32 channel;
> ÂÂÂÂ__le32 host;
> ÂÂÂÂ__le32 domain;
> };
>
> Then we can provide a vendor resource hook in the remoteproc driver to
> handle
> these resources:
>
> static int ti_pru_rproc_handle_rsc(struct rproc *rproc, u32 rsc_type,
> void *rsc,
> ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ int offset, int avail)
> {
> ÂÂÂÂstruct ti_pru_data *pru = rproc->priv;
> ÂÂÂÂstruct irq_fwspec fwspec;
> ÂÂÂÂunsigned int virq;
>
> ÂÂÂÂswitch (rsc_type) {
> ÂÂÂÂcase TI_PRU_VENDOR_RESOURCE_IRQ:
> ÂÂÂÂ{
> ÂÂÂÂÂÂÂ struct ti_pru_vendor_resource_irq *rsc_irq = rsc;
>
> ÂÂÂÂÂÂÂ fwspec.fwnode = pru->intc_fwnode;
> ÂÂÂÂÂÂÂ fwspec.param[0] = le32_to_cpu(rsc_irq->event);
> ÂÂÂÂÂÂÂ fwspec.param[1] = le32_to_cpu(rsc_irq->channel);
> ÂÂÂÂÂÂÂ fwspec.param[2] = le32_to_cpu(rsc_irq->host);
> ÂÂÂÂÂÂÂ fwspec.param[3] = le32_to_cpu(rsc_irq->domain);
> ÂÂÂÂÂÂÂ fwspec.param_count = 4;
> ÂÂÂÂ}
> ÂÂÂÂÂÂÂ break;
> ÂÂÂÂcase TI_PRU_VENDOR_RESOURCE_IRQ2:
> ÂÂÂÂ{
> ÂÂÂÂÂÂÂ struct ti_pru_vendor_resource_irq2 *rsc_irq2 = rsc;
>
> ÂÂÂÂÂÂÂ fwspec.fwnode = pru->intc_fwnode;
> ÂÂÂÂÂÂÂ fwspec.param[0] = le32_to_cpu(rsc_irq2->event);
> ÂÂÂÂÂÂÂ fwspec.param[1] = le32_to_cpu(rsc_irq2->evt_sel);
> ÂÂÂÂÂÂÂ fwspec.param[2] = le32_to_cpu(rsc_irq2->channel);
> ÂÂÂÂÂÂÂ fwspec.param[3] = le32_to_cpu(rsc_irq2->host);
> ÂÂÂÂÂÂÂ fwspec.param[4] = le32_to_cpu(rsc_irq2->domain);
> ÂÂÂÂÂÂÂ fwspec.param_count = 5;
> ÂÂÂÂÂÂÂ break;
> ÂÂÂÂ}
> ÂÂÂÂdefault:
> ÂÂÂÂÂÂÂ return RSC_IGNORED;
> ÂÂÂÂ}
>
> ÂÂÂÂvirq = irq_create_fwspec_mapping(&fwspec);
> ÂÂÂÂif (!virq)
> ÂÂÂÂÂÂÂ return -EINVAL;
>
> ÂÂÂÂ/* TODO: save virq (and other metadata) for later use */
>
> ÂÂÂÂreturn RSC_HANDLED;
> }
>
> static const struct rproc_ops ti_pru_rproc_ops = {
> ÂÂÂÂ.start = ti_pru_rproc_start,
> ÂÂÂÂ.stop = ti_pru_rproc_stop,
> ÂÂÂÂ.kick = ti_pru_rproc_kick,
> ÂÂÂÂ.da_to_va = ti_pru_rproc_da_to_va,
> ÂÂÂÂ.handle_rsc = ti_pru_rproc_handle_rsc,
> };
>
> The handle_rsc callback is called for each resource when the PRU is booted.
> The function irq_create_fwspec_mapping() causes the IRQ to be mapped in
> hardware. From what I understand from the previous discussions, this is
> exactly
> when we want this to happen.
>
> This patch applies on top of "irqchip/irq-pruss-intc: Add a PRUSS
> irqchip driver
> for PRUSS interrupts", "irqchip/irq-pruss-intc: Add support for shared and
> invalid interrupts" and "irqchip/irq-pruss-intc: Implement irq_{get,set}
> _irqchip_state ops" from [PATCH v2 0/6] "Add TI PRUSS Local Interrupt
> Controller
> IRQChip driver" [2].
>
> A working copy along with some remoteproc and rpmsg hacks can be found
> on my
> GitHub [3].
>
> [1]:
> https://lore.kernel.org/lkml/fb2bdb7b-4d4d-508f-722a-554888280145@xxxxxxxxxxxxxx/
>
> [2]: https://lore.kernel.org/lkml/20190731224149.11153-1-s-anna@xxxxxx/
> [3]: https://github.com/dlech/linux/commits/pruss-2019-08-08
>
> Signed-off-by: Suman Anna <s-anna@xxxxxx>
> Signed-off-by: Andrew F. Davis <afd@xxxxxx>
> Signed-off-by: Roger Quadros <rogerq@xxxxxx>
> Signed-off-by: David Lechner <david@xxxxxxxxxxxxxx>
> ---
> Âdrivers/irqchip/irq-pruss-intc.cÂÂÂÂÂÂÂÂÂÂÂÂÂ | 387 +++++++++++++++++-
> Â.../interrupt-controller/ti-pruss.hÂÂÂÂÂÂÂÂÂÂ |Â 27 ++
> Â2 files changed, 396 insertions(+), 18 deletions(-)
> Âcreate mode 100644 include/dt-bindings/interrupt-controller/ti-pruss.h
>
> diff --git a/drivers/irqchip/irq-pruss-intc.c
> b/drivers/irqchip/irq-pruss-intc.c
> index c1fd6c09f2f2..da4349df08c3 100644
> --- a/drivers/irqchip/irq-pruss-intc.c
> +++ b/drivers/irqchip/irq-pruss-intc.c
> @@ -5,6 +5,8 @@
> Â * Copyright (C) 2016-2019 Texas Instruments Incorporated -
> http://www.ti.com/
> Â *ÂÂÂ Andrew F. Davis <afd@xxxxxx>
> Â *ÂÂÂ Suman Anna <s-anna@xxxxxx>
> + *
> + * Copyright (C) 2019 David Lechner <david@xxxxxxxxxxxxxx>
> Â */
> Â
> Â#include <linux/interrupt.h>
> @@ -14,6 +16,14 @@
> Â#include <linux/module.h>
> Â#include <linux/of_device.h>
> Â#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +
> +#include <dt-bindings/interrupt-controller/ti-pruss.h>
> +
> +/* The number of possible interrupt domains, see TI_PRUSS_INTC_DOMAIN_* in
> + * dt-bindings/interrupt-controller/ti-pruss.h
> + */
> +#define NUM_TI_PRUSS_INTC_DOMAIN 5
> Â
> Â/*
> Â * Number of host interrupts reaching the main MPU sub-system. Note
> that this
> @@ -25,6 +35,12 @@
> Â/* minimum starting host interrupt number for MPU */
> Â#define MIN_PRU_HOST_INTÂÂÂ 2
> Â
> +/* maximum number of host interrupts */
> +#define MAX_PRU_HOST_INTÂÂÂ 10
> +
> +/* maximum number of interrupt channels */
> +#define MAX_PRU_CHANNELSÂÂÂ 10
> +
> Â/* maximum number of system events */
> Â#define MAX_PRU_SYS_EVENTSÂÂÂ 64
> Â
> @@ -57,27 +73,83 @@
> Â#define PRU_INTC_HINLR(x)ÂÂÂ (0x1100 + (x) * 4)
> Â#define PRU_INTC_HIERÂÂÂÂÂÂÂ 0x1500
> Â
> +/* CMR register bit-field macros */
> +#define CMR_EVT_MAP_MASKÂÂÂ 0xf
> +#define CMR_EVT_MAP_BITSÂÂÂ 8
> +#define CMR_EVT_PER_REGÂÂÂÂÂÂÂ 4
> +
> +/* HMR register bit-field macros */
> +#define HMR_CH_MAP_MASKÂÂÂÂÂÂÂ 0xf
> +#define HMR_CH_MAP_BITSÂÂÂÂÂÂÂ 8
> +#define HMR_CH_PER_REGÂÂÂÂÂÂÂ 4
> +
> Â/* HIPIR register bit-fields */
> Â#define INTC_HIPIR_NONE_HINTÂÂÂ 0x80000000
> Â
> +/**
> + * struct pruss_intc_hwirq_data - additional metadata associated with a
> PRU
> + * system event
> + * @evtsel: The event select index (AM18xx only)
> + * @channel: The PRU INTC channel that the system event should be
> mapped to
> + * @host: The PRU INTC host that the channel should be mapped to
> + */
> +struct pruss_intc_hwirq_data {
> +ÂÂÂ u8 evtsel;
> +ÂÂÂ u8 channel;
> +ÂÂÂ u8 host;
> +};
> +
> +/**
> + * struct pruss_intc_map_record - keeps track of actual mapping state
> + * @value: The currently mapped value (evtsel, channel or host)
> + * @ref_count: Keeps track of number of current users of this resource
> + */
> +struct pruss_intc_map_record {
> +ÂÂÂ u8 value;
> +ÂÂÂ u8 ref_count;
> +};
> +
> +/**
> + * struct pruss_intc_domain - information specific to an external IRQ
> domain
> + * @hwirq_data: Table of additional mapping data received from device tree
> + *ÂÂÂ or PRU firmware
> + * @domain: irq domain
> + * @intc: the interrupt controller
> + * @id: Unique domain identifier (from device tree bindings)
> + */
> +struct pruss_intc_domain {
> +ÂÂÂ struct pruss_intc_hwirq_data hwirq_data[MAX_PRU_SYS_EVENTS];
> +ÂÂÂ struct irq_domain *domain;
> +ÂÂÂ struct pruss_intc *intc;
> +ÂÂÂ u32 id;
> +};
> +
> Â/**
> Â * struct pruss_intc - PRUSS interrupt controller structure
> + * @domain: External interrupt domains
> + * @evtsel: Tracks the current state of CFGCHIP3[3].PRUSSEVTSEL (AM18xx
> only)
> + * @event_channel: Tracks the current state of system event to channel
> mappings
> + * @channel_host: Tracks the current state of channel to host mappings
> Â * @irqs: kernel irq numbers corresponding to PRUSS host interrupts
> Â * @base: base virtual address of INTC register space
> Â * @irqchip: irq chip for this interrupt controller
> - * @domain: irq domain for this interrupt controller
> Â * @lock: mutex to serialize access to INTC
> Â * @shared_intr: bit-map denoting if the MPU host interrupt is shared
> Â * @invalid_intr: bit-map denoting if host interrupt is not connected
> to MPU
> + * @has_evtsel: indicates that the chip has an event select mux
> Â */
> Âstruct pruss_intc {
> +ÂÂÂ struct pruss_intc_domain domain[NUM_ISA_INTERRUPTS];
> +ÂÂÂ struct pruss_intc_map_record evtsel;
> +ÂÂÂ struct pruss_intc_map_record event_channel[MAX_PRU_SYS_EVENTS];
> +ÂÂÂ struct pruss_intc_map_record channel_host[MAX_PRU_CHANNELS];
> ÂÂÂÂ unsigned int irqs[MAX_NUM_HOST_IRQS];
> ÂÂÂÂ void __iomem *base;
> ÂÂÂÂ struct irq_chip *irqchip;
> -ÂÂÂ struct irq_domain *domain;
> ÂÂÂÂ struct mutex lock; /* PRUSS INTC lock */
> ÂÂÂÂ u16 shared_intr;
> ÂÂÂÂ u16 invalid_intr;
> +ÂÂÂ bool has_evtsel;
> Â};
> Â
> Âstatic inline u32 pruss_intc_read_reg(struct pruss_intc *intc, unsigned
> int reg)
> @@ -105,6 +177,172 @@ static int pruss_intc_check_write(struct
> pruss_intc *intc, unsigned int reg,
> ÂÂÂÂ return 0;
> Â}
> Â
> +/**
> + * pruss_intc_map() - configure the PRUSS INTC
> + * @domain: pru intc domain pointer
> + * @hwirq: the system event number
> + *
> + * Configures the PRUSS INTC with the provided configuration from the one
> + * parsed in the xlate function. Any existing event to channel mappings or
> + * channel to host interrupt mappings are checked to make sure there
> are no
> + * conflicting configuration between both the PRU cores.
> + *
> + * Returns 0 on success, or a suitable error code otherwise
> + */
> +static int pruss_intc_map(struct pruss_intc_domain *domain, unsigned
> long hwirq)
> +{
> +ÂÂÂ struct pruss_intc *intc = domain->intc;
> +ÂÂÂ struct device* dev = intc->irqchip->parent_device;
> +ÂÂÂ u32 val;
> +ÂÂÂ int idx, ret;
> +ÂÂÂ u8 evtsel, ch, host;
> +
> +ÂÂÂ if (hwirq >= MAX_PRU_SYS_EVENTS)
> +ÂÂÂÂÂÂÂ return -EINVAL;
> +
> +ÂÂÂ mutex_lock(&intc->lock);
> +
> +ÂÂÂ evtsel = domain->hwirq_data[hwirq].evtsel;
> +ÂÂÂ ch = domain->hwirq_data[hwirq].channel;
> +ÂÂÂ host = domain->hwirq_data[hwirq].host;
> +
> +ÂÂÂ if (intc->has_evtsel && intc->evtsel.ref_count > 0 &&
> +ÂÂÂÂÂÂÂ intc->evtsel.value != evtsel) {
> +ÂÂÂÂÂÂÂ dev_err(dev, "event %lu (req. evtsel %d) already assigned to
> evtsel %d\n",
> +ÂÂÂÂÂÂÂÂÂÂÂ hwirq, evtsel, intc->evtsel.value);
> +ÂÂÂÂÂÂÂ ret = -EBUSY;
> +ÂÂÂÂÂÂÂ goto unlock;
> +ÂÂÂ }
> +
> +ÂÂÂ /* check if sysevent already assigned */
> +ÂÂÂ if (intc->event_channel[hwirq].ref_count > 0 &&
> +ÂÂÂÂÂÂÂ intc->event_channel[hwirq].value != ch) {
> +ÂÂÂÂÂÂÂ dev_err(dev, "event %lu (req. channel %d) already assigned to
> channel %d\n",
> +ÂÂÂÂÂÂÂÂÂÂÂ hwirq, ch, intc->event_channel[hwirq].value);
> +ÂÂÂÂÂÂÂ ret = -EBUSY;
> +ÂÂÂÂÂÂÂ goto unlock;
> +ÂÂÂ }
> +
> +ÂÂÂ /* check if channel already assigned */
> +ÂÂÂ if (intc->channel_host[ch].ref_count > 0 &&
> +ÂÂÂÂÂÂÂ intc->channel_host[ch].value != host) {
> +ÂÂÂÂÂÂÂ dev_err(dev, "channel %d (req. host %d) already assigned to
> host %d\n",
> +ÂÂÂÂÂÂÂÂÂÂÂ ch, host, intc->channel_host[ch].value);
> +ÂÂÂÂÂÂÂ ret = -EBUSY;
> +ÂÂÂÂÂÂÂ goto unlock;
> +ÂÂÂ }
> +
> +ÂÂÂ if (++intc->evtsel.ref_count == 1) {
> +ÂÂÂÂÂÂÂ intc->evtsel.value = evtsel;
> +
> +ÂÂÂÂÂÂÂ /* TODO: need to implement CFGCHIP3[3].PRUSSEVTSEL */
> +ÂÂÂ }
> +
> +ÂÂÂ if (++intc->event_channel[hwirq].ref_count == 1) {
> +ÂÂÂÂÂÂÂ intc->event_channel[hwirq].value = ch;
> +
> +ÂÂÂÂÂÂÂ /*
> +ÂÂÂÂÂÂÂÂ * configure channel map registers - each register holds map
> +ÂÂÂÂÂÂÂÂ * info for 4 events, with each event occupying the lower nibble
> +ÂÂÂÂÂÂÂÂ * in a register byte address in little-endian fashion
> +ÂÂÂÂÂÂÂÂ */
> +ÂÂÂÂÂÂÂ idx = hwirq / CMR_EVT_PER_REG;
> +
> +ÂÂÂÂÂÂÂ val = pruss_intc_read_reg(intc, PRU_INTC_CMR(idx));
> +ÂÂÂÂÂÂÂ val &= ~(CMR_EVT_MAP_MASK <<
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ ((hwirq % CMR_EVT_PER_REG) * CMR_EVT_MAP_BITS));
> +ÂÂÂÂÂÂÂ val |= ch << ((hwirq % CMR_EVT_PER_REG) * CMR_EVT_MAP_BITS);
> +ÂÂÂÂÂÂÂ pruss_intc_write_reg(intc, PRU_INTC_CMR(idx), val);
> +
> +ÂÂÂÂÂÂÂ dev_dbg(dev, "SYSEV%lu -> CH%d (CMR%d 0x%08x)\n", hwirq, ch,
> +ÂÂÂÂÂÂÂÂÂÂÂ idx, pruss_intc_read_reg(intc, PRU_INTC_CMR(idx)));
> +
> +ÂÂÂÂÂÂÂ /* clear and enable system event */
> +ÂÂÂÂÂÂÂ pruss_intc_write_reg(intc, PRU_INTC_SICR, hwirq);
> +ÂÂÂÂÂÂÂ pruss_intc_write_reg(intc, PRU_INTC_EISR, hwirq);
> +ÂÂÂ }
> +
> +ÂÂÂ if (++intc->channel_host[ch].ref_count == 1) {
> +ÂÂÂÂÂÂÂ intc->channel_host[ch].value = host;
> +
> +ÂÂÂÂÂÂÂ /*
> +ÂÂÂÂÂÂÂÂ * set host map registers - each register holds map info for
> +ÂÂÂÂÂÂÂÂ * 4 channels, with each channel occupying the lower nibble in
> +ÂÂÂÂÂÂÂÂ * a register byte address in little-endian fashion
> +ÂÂÂÂÂÂÂÂ */
> +ÂÂÂÂÂÂÂ idx = ch / HMR_CH_PER_REG;
> +
> +ÂÂÂÂÂÂÂ val = pruss_intc_read_reg(intc, PRU_INTC_HMR(idx));
> +ÂÂÂÂÂÂÂ val &= ~(HMR_CH_MAP_MASK <<
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ ((ch % HMR_CH_PER_REG) * HMR_CH_MAP_BITS));
> +ÂÂÂÂÂÂÂ val |= host << ((ch % HMR_CH_PER_REG) * HMR_CH_MAP_BITS);
> +ÂÂÂÂÂÂÂ pruss_intc_write_reg(intc, PRU_INTC_HMR(idx), val);
> +
> +ÂÂÂÂÂÂÂ dev_dbg(dev, "CH%d -> HOST%d (HMR%d 0x%08x)\n", ch, host, idx,
> +ÂÂÂÂÂÂÂÂÂÂÂ pruss_intc_read_reg(intc, PRU_INTC_HMR(idx)));
> +
> +ÂÂÂÂÂÂÂ /* enable host interrupts */
> +ÂÂÂÂÂÂÂ pruss_intc_write_reg(intc, PRU_INTC_HIEISR, host);
> +ÂÂÂ }
> +
> +ÂÂÂ dev_info(dev, "mapped system_event = %lu channel = %d host = %d
> domain = %u\n",
> +ÂÂÂÂÂÂÂÂ hwirq, ch, host, domain->id);
> +
> +ÂÂÂ /* global interrupt enable */
> +ÂÂÂ pruss_intc_write_reg(intc, PRU_INTC_GER, 1);
> +
> +ÂÂÂ mutex_unlock(&intc->lock);
> +ÂÂÂ return 0;
> +
> +unlock:
> +ÂÂÂ mutex_unlock(&intc->lock);
> +ÂÂÂ return ret;
> +}
> +
> +/**
> + * pruss_intc_unmap() - unconfigure the PRUSS INTC
> + * @domain: pru intc domain pointer
> + * @hwirq: the system event number
> + *
> + * Undo whatever was done in pruss_intc_map() for a PRU core.
> + * Mappings are reference counted, so resources are only disabled when
> there
> + * are no longer any users.
> + */
> +static void pruss_intc_unmap(struct pruss_intc_domain *domain, unsigned
> long hwirq)
> +{
> +ÂÂÂ struct pruss_intc *intc = domain->intc;
> +ÂÂÂ struct device* dev = intc->irqchip->parent_device;
> +ÂÂÂ u8 ch, host;
> +
> +ÂÂÂ if (hwirq >= MAX_PRU_SYS_EVENTS)
> +ÂÂÂÂÂÂÂ return;
> +
> +ÂÂÂ mutex_lock(&intc->lock);
> +
> +ÂÂÂ ch = intc->event_channel[hwirq].value;
> +ÂÂÂ host = intc->channel_host[ch].value;
> +
> +ÂÂÂ if (--intc->channel_host[ch].ref_count == 0) {
> +ÂÂÂÂÂÂÂ /* disable host interrupts */
> +ÂÂÂÂÂÂÂ pruss_intc_write_reg(intc, PRU_INTC_HIDISR, host);
> +ÂÂÂ }
> +
> +ÂÂÂ if (--intc->event_channel[hwirq].ref_count == 0) {
> +ÂÂÂÂÂÂÂ /* disable system events */
> +ÂÂÂÂÂÂÂ pruss_intc_write_reg(intc, PRU_INTC_EICR, hwirq);
> +ÂÂÂÂÂÂÂ /* clear any pending status */
> +ÂÂÂÂÂÂÂ pruss_intc_write_reg(intc, PRU_INTC_SICR, hwirq);
> +ÂÂÂ }
> +
> +ÂÂÂ if (intc->has_evtsel)
> +ÂÂÂÂÂÂÂ intc->evtsel.ref_count--;
> +
> +ÂÂÂ dev_info(dev, "unmapped system_event = %lu channel = %d host = %d\n",
> +ÂÂÂÂÂÂÂÂ hwirq, ch, host);
> +
> +ÂÂÂ mutex_unlock(&intc->lock);
> +}
> +
> Âstatic void pruss_intc_init(struct pruss_intc *intc)
> Â{
> ÂÂÂÂ int i;
> @@ -198,10 +436,83 @@ static int pruss_intc_irq_set_irqchip_state(struct
> irq_data *data,
> ÂÂÂÂ return pruss_intc_check_write(intc, PRU_INTC_SICR, data->hwirq);
> Â}
> Â
> +static int pruss_intc_irq_domain_select(struct irq_domain *d,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ struct irq_fwspec *fwspec,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ enum irq_domain_bus_token bus_token)
> +{
> +ÂÂÂ struct pruss_intc_domain *domain = d->host_data;
> +ÂÂÂ int num_cells = domain->intc->has_evtsel ? 5 : 4;
> +ÂÂÂ u32 domain_id;
> +
> +ÂÂÂ if (!fwspec || fwspec->fwnode != domain->domain->fwnode)
> +ÂÂÂÂÂÂÂ return 0;
> +
> +ÂÂÂ if (bus_token != DOMAIN_BUS_ANY && bus_token !=
> domain->domain->bus_token)
> +ÂÂÂÂÂÂÂ return 0;
> +
> +ÂÂÂ if (WARN_ON_ONCE(fwspec->param_count != num_cells))
> +ÂÂÂÂÂÂÂ return 0;
> +
> +ÂÂÂ domain_id = fwspec->param[fwspec->param_count - 1];
> +ÂÂÂ if (domain_id != domain->id)
> +ÂÂÂÂÂÂÂ return 0;
> +
> +ÂÂÂ return 1;
> +}
> +
> +static int
> +pruss_intc_irq_domain_xlate(struct irq_domain *d, struct device_node
> *node,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ const u32 *intspec, unsigned int intsize,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ unsigned long *out_hwirq, unsigned int *out_type)
> +{
> +ÂÂÂ struct pruss_intc_domain *domain = d->host_data;
> +ÂÂÂ struct pruss_intc *intc = domain->intc;
> +ÂÂÂ int num_cells = intc->has_evtsel ? 5 : 4;
> +ÂÂÂ u32 sys_event, channel, host, domain_id;
> +ÂÂÂ u32 evtsel = 0;
> +
> +ÂÂÂ if (WARN_ON_ONCE(intsize != num_cells))
> +ÂÂÂÂÂÂÂ return -EINVAL;
> +
> +ÂÂÂ sys_event = intspec[0];
> +ÂÂÂ if (sys_event >= MAX_PRU_SYS_EVENTS)
> +ÂÂÂÂÂÂÂ return -EINVAL;
> +
> +ÂÂÂ if (intc->has_evtsel)
> +ÂÂÂÂÂÂÂ evtsel = intspec[1];
> +
> +ÂÂÂ channel = intspec[intsize - 3];
> +ÂÂÂ if (channel >= MAX_PRU_CHANNELS)
> +ÂÂÂÂÂÂÂ return -EINVAL;
> +
> +ÂÂÂ host = intspec[intsize - 2];
> +ÂÂÂ if (host >= MAX_PRU_HOST_INT)
> +ÂÂÂÂÂÂÂ return -EINVAL;
> +
> +ÂÂÂ domain_id = intspec[intsize - 1];
> +ÂÂÂ if (domain_id != domain->id)
> +ÂÂÂÂÂÂÂ return -EINVAL;
> +
> +ÂÂÂ domain->hwirq_data[sys_event].evtsel = evtsel;
> +ÂÂÂ domain->hwirq_data[sys_event].channel = channel;
> +ÂÂÂ domain->hwirq_data[sys_event].host = host;
> +
> +ÂÂÂ *out_hwirq = sys_event;
> +ÂÂÂ *out_type = IRQ_TYPE_NONE;
> +
> +ÂÂÂ return 0;
> +}
> +
> Âstatic int pruss_intc_irq_domain_map(struct irq_domain *d, unsigned int
> virq,
> ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ irq_hw_number_t hw)
> Â{
> -ÂÂÂ struct pruss_intc *intc = d->host_data;
> +ÂÂÂ struct pruss_intc_domain *domain = d->host_data;
> +ÂÂÂ struct pruss_intc *intc = domain->intc;
> +ÂÂÂ int err;
> +
> +ÂÂÂ err = pruss_intc_map(domain, hw);
> +ÂÂÂ if (err < 0)
> +ÂÂÂÂÂÂÂ return err;
> Â
> ÂÂÂÂ irq_set_chip_data(virq, intc);
> ÂÂÂÂ irq_set_chip_and_handler(virq, intc->irqchip, handle_level_irq);
> @@ -211,12 +522,17 @@ static int pruss_intc_irq_domain_map(struct
> irq_domain *d, unsigned int virq,
> Â
> Âstatic void pruss_intc_irq_domain_unmap(struct irq_domain *d, unsigned
> int virq)
> Â{
> +ÂÂÂ struct pruss_intc_domain *domain = d->host_data;
> +ÂÂÂ unsigned long hwirq = irqd_to_hwirq(irq_get_irq_data(virq));
> +
> ÂÂÂÂ irq_set_chip_and_handler(virq, NULL, NULL);
> ÂÂÂÂ irq_set_chip_data(virq, NULL);
> +ÂÂÂ pruss_intc_unmap(domain, hwirq);
> Â}
> Â
> Âstatic const struct irq_domain_ops pruss_intc_irq_domain_ops = {
> -ÂÂÂ .xlateÂÂÂ = irq_domain_xlate_onecell,
> +ÂÂÂ .selectÂÂÂ = pruss_intc_irq_domain_select,
> +ÂÂÂ .xlateÂÂÂ = pruss_intc_irq_domain_xlate,
> ÂÂÂÂ .mapÂÂÂ = pruss_intc_irq_domain_map,
> ÂÂÂÂ .unmapÂÂÂ = pruss_intc_irq_domain_unmap,
> Â};
> @@ -245,7 +561,8 @@ static void pruss_intc_irq_handler(struct irq_desc
> *desc)
> ÂÂÂÂ hipir = pruss_intc_read_reg(intc, PRU_INTC_HIPIR(i));
> ÂÂÂÂ while (!(hipir & INTC_HIPIR_NONE_HINT)) {
> ÂÂÂÂÂÂÂÂ hwirq = hipir & GENMASK(9, 0);
> -ÂÂÂÂÂÂÂ virq = irq_linear_revmap(intc->domain, hwirq);
> +ÂÂÂÂÂÂÂ virq = irq_linear_revmap(
> +ÂÂÂÂÂÂÂÂÂÂÂ intc->domain[TI_PRUSS_INTC_DOMAIN_MCU].domain, hwirq);
> Â
> ÂÂÂÂÂÂÂÂ /*
> ÂÂÂÂÂÂÂÂÂ * NOTE: manually ACK any system events that do not have a
> @@ -272,7 +589,8 @@ static int pruss_intc_probe(struct platform_device
> *pdev)
> ÂÂÂÂ struct pruss_intc *intc;
> ÂÂÂÂ struct resource *res;
> ÂÂÂÂ struct irq_chip *irqchip;
> -ÂÂÂ int i, irq, count;
> +ÂÂÂ int i, err, irq, count;
> +ÂÂÂ u32 num_cells;
> ÂÂÂÂ u8 temp_intr[MAX_NUM_HOST_IRQS] = { 0 };
> Â
> ÂÂÂÂ intc = devm_kzalloc(dev, sizeof(*intc), GFP_KERNEL);
> @@ -323,13 +641,22 @@ static int pruss_intc_probe(struct platform_device
> *pdev)
> ÂÂÂÂÂÂÂÂ }
> ÂÂÂÂ }
> Â
> +ÂÂÂ err = of_property_read_u32(dev->of_node, "#interrupt-cells",
> &num_cells);
> +ÂÂÂ if (!err && num_cells == 5)
> +ÂÂÂÂÂÂÂ intc->has_evtsel = true;
> +
> ÂÂÂÂ mutex_init(&intc->lock);
> Â
> +ÂÂÂ pm_runtime_enable(dev);
> +ÂÂÂ pm_runtime_get_sync(dev);
> +
> ÂÂÂÂ pruss_intc_init(intc);
> Â
> ÂÂÂÂ irqchip = devm_kzalloc(dev, sizeof(*irqchip), GFP_KERNEL);
> -ÂÂÂ if (!irqchip)
> -ÂÂÂÂÂÂÂ return -ENOMEM;
> +ÂÂÂ if (!irqchip) {
> +ÂÂÂÂÂÂÂ err = -ENOMEM;
> +ÂÂÂÂÂÂÂ goto fail_alloc;
> +ÂÂÂ }
> Â
> ÂÂÂÂ irqchip->irq_ack = pruss_intc_irq_ack;
> ÂÂÂÂ irqchip->irq_mask = pruss_intc_irq_mask;
> @@ -338,14 +665,24 @@ static int pruss_intc_probe(struct platform_device
> *pdev)
> ÂÂÂÂ irqchip->irq_release_resources = pruss_intc_irq_relres;
> ÂÂÂÂ irqchip->irq_get_irqchip_state = pruss_intc_irq_get_irqchip_state;
> ÂÂÂÂ irqchip->irq_set_irqchip_state = pruss_intc_irq_set_irqchip_state;
> +ÂÂÂ irqchip->parent_device = dev;
> ÂÂÂÂ irqchip->name = dev_name(dev);
> ÂÂÂÂ intc->irqchip = irqchip;
> Â
> -ÂÂÂ /* always 64 events */
> -ÂÂÂ intc->domain = irq_domain_add_linear(dev->of_node, MAX_PRU_SYS_EVENTS,
> -ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ &pruss_intc_irq_domain_ops, intc);
> -ÂÂÂ if (!intc->domain)
> -ÂÂÂÂÂÂÂ return -ENOMEM;
> +ÂÂÂ for (i = 0; i < NUM_TI_PRUSS_INTC_DOMAIN; i++) {
> +ÂÂÂÂÂÂÂ intc->domain[i].intc = intc;
> +ÂÂÂÂÂÂÂ intc->domain[i].id = i;
> +ÂÂÂÂÂÂÂ /* always 64 events */
> +ÂÂÂÂÂÂÂ intc->domain[i].domain = irq_domain_add_linear(dev->of_node,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ MAX_PRU_SYS_EVENTS, &pruss_intc_irq_domain_ops,
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ &intc->domain[i]);
> +ÂÂÂÂÂÂÂ if (!intc->domain[i].domain) {
> +ÂÂÂÂÂÂÂÂÂÂÂ while (--i >= 0)
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ irq_domain_remove(intc->domain[i].domain);
> +ÂÂÂÂÂÂÂÂÂÂÂ err = -ENOMEM;
> +ÂÂÂÂÂÂÂÂÂÂÂ goto fail_alloc;
> +ÂÂÂÂÂÂÂ }
> +ÂÂÂ }
> Â
> ÂÂÂÂ for (i = 0; i < MAX_NUM_HOST_IRQS; i++) {
> ÂÂÂÂÂÂÂÂ irq = platform_get_irq_byname(pdev, irq_names[i]);
> @@ -356,6 +693,7 @@ static int pruss_intc_probe(struct platform_device
> *pdev)
> Â
> ÂÂÂÂÂÂÂÂÂÂÂÂ dev_err(dev, "platform_get_irq_byname failed for %s : %d\n",
> ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ irq_names[i], irq);
> +ÂÂÂÂÂÂÂÂÂÂÂ err = irq;
> ÂÂÂÂÂÂÂÂÂÂÂÂ goto fail_irq;
> ÂÂÂÂÂÂÂÂ }
> Â
> @@ -372,13 +710,20 @@ static int pruss_intc_probe(struct platform_device
> *pdev)
> ÂÂÂÂÂÂÂÂÂÂÂÂ irq_set_chained_handler_and_data(intc->irqs[i], NULL,
> ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ NULL);
> ÂÂÂÂ }
> -ÂÂÂ irq_domain_remove(intc->domain);
> -ÂÂÂ return irq;
> +ÂÂÂ for (i = 0; i < NUM_TI_PRUSS_INTC_DOMAIN; i++)
> +ÂÂÂÂÂÂÂ irq_domain_remove(intc->domain[i].domain);
> +
> +fail_alloc:
> +ÂÂÂ pm_runtime_put(dev);
> +ÂÂÂ pm_runtime_disable(dev);
> +
> +ÂÂÂ return err;
> Â}
> Â
> Âstatic int pruss_intc_remove(struct platform_device *pdev)
> Â{
> ÂÂÂÂ struct pruss_intc *intc = platform_get_drvdata(pdev);
> +ÂÂÂ struct device *dev = &pdev->dev;
> ÂÂÂÂ unsigned int hwirq;
> ÂÂÂÂ int i;
> Â
> @@ -388,9 +733,15 @@ static int pruss_intc_remove(struct platform_device
> *pdev)
> ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ NULL);
> ÂÂÂÂ }
> Â
> -ÂÂÂ for (hwirq = 0; hwirq < MAX_PRU_SYS_EVENTS; hwirq++)
> -ÂÂÂÂÂÂÂ irq_dispose_mapping(irq_find_mapping(intc->domain, hwirq));
> -ÂÂÂ irq_domain_remove(intc->domain);
> +ÂÂÂ for (i = 0; i < NUM_TI_PRUSS_INTC_DOMAIN; i++) {
> +ÂÂÂÂÂÂÂ for (hwirq = 0; hwirq < MAX_PRU_SYS_EVENTS; hwirq++)
> +ÂÂÂÂÂÂÂÂÂÂÂ irq_dispose_mapping(irq_find_mapping(
> +ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ intc->domain[i].domain, hwirq));
> +ÂÂÂÂÂÂÂ irq_domain_remove(intc->domain[i].domain);
> +ÂÂÂ }
> +
> +ÂÂÂ pm_runtime_put(dev);
> +ÂÂÂ pm_runtime_disable(dev);
> Â
> ÂÂÂÂ return 0;
> Â}
> diff --git a/include/dt-bindings/interrupt-controller/ti-pruss.h
> b/include/dt-bindings/interrupt-controller/ti-pruss.h
> new file mode 100644
> index 000000000000..326a68c31bce
> --- /dev/null
> +++ b/include/dt-bindings/interrupt-controller/ti-pruss.h
> @@ -0,0 +1,27 @@
> +/* SPDX-License-Identifier: GPL-2.0 OR MIT */
> +/*
> + * This header provides constants for the Texas Instruments Programmable
> + * Realtime Unit Subsystem (PRUSS) interrupt controller.
> + */
> +
> +#ifndef _DT_BINDINGS_INTERRUPT_CONTROLLER_TI_PRUSS_H
> +#define _DT_BINDINGS_INTERRUPT_CONTROLLER_TI_PRUSS_H
> +
> +/* interrupt specifier for optional cell 1 */
> +
> +#define TI_PRUSS_INTC_EVTSEL_ANYÂÂÂ 0xffffffff
> +
> +/* interrupt specifier for cell #interrupt-cells - 1 */
> +
> +/* host interrupt is connected to PRU cores, e.g. host events 0 and 1 */
> +#define TI_PRUSS_INTC_DOMAIN_PRUÂÂÂ 0
> +/* host interrupt is connected to MCU's interrupt controller */
> +#define TI_PRUSS_INTC_DOMAIN_MCUÂÂÂ 1
> +/* host interrupt is connected to DSP's interrupt controller */
> +#define TI_PRUSS_INTC_DOMAIN_DSPÂÂÂ 2
> +/* host interrupt is connected to the auxillary PRU cores */
> +#define TI_PRUSS_INTC_DOMAIN_RTU_PRUÂÂÂ 3
> +/* host interrupt is connected to the task managers */
> +#define TI_PRUSS_INTC_DOMAIN_TASKÂÂÂ 4
> +
> +#endif /* _DT_BINDINGS_INTERRUPT_CONTROLLER_TI_PRUSS_H */