// SPDX-License-Identifier: GPL-2.0-only
/* SPDX-FileCopyrightText: Copyright (c) 2008-2024, NVIDIA CORPORATION.  All rights reserved. */

#include "mods_internal.h"

#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/interrupt.h>
#include <linux/pci_regs.h>
#if defined(MODS_HAS_TEGRA) && defined(CONFIG_OF) && defined(CONFIG_OF_IRQ)
#include <linux/of_irq.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/of_platform.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#endif

#define PCI_VENDOR_ID_NVIDIA 0x10de
#define INDEX_IRQSTAT(irq)	(irq / BITS_NUM)
#define POS_IRQSTAT(irq)	 (irq & (BITS_NUM - 1))

/* MSI */
#define PCI_MSI_MASK_BIT	16
#define MSI_CONTROL_REG(base)		(base + PCI_MSI_FLAGS)
#define IS_64BIT_ADDRESS(control)	(!!(control & PCI_MSI_FLAGS_64BIT))
#define MSI_DATA_REG(base, is64bit) \
	((is64bit == 1) ? base + PCI_MSI_DATA_64 : base + PCI_MSI_DATA_32)
#define TOP_TKE_TKEIE_WDT_MASK(i)	(1 << (16 + 4 * (i)))
#define TOP_TKE_TKEIE(i)		(0x100 + 4 * (i))

/*********************
 * PRIVATE FUNCTIONS *
 *********************/

/* Mutex for guarding interrupt logic and PCI device enablement */
static struct mutex irq_mtx;

struct mutex *mods_get_irq_mutex(void)
{
	return &irq_mtx;
}

#ifdef CONFIG_PCI
int mods_enable_device(struct mods_client   *client,
		       struct pci_dev       *dev,
		       struct en_dev_entry **dev_entry)
{
	int                  err   = OK;
	struct en_dev_entry *dpriv = NULL;

	WARN_ON(!mutex_is_locked(&irq_mtx));

	dpriv = pci_get_drvdata(dev);
	if (!dpriv) {
		cl_error(
			"driver data is not set for %04x:%02x:%02x.%x\n",
			pci_domain_nr(dev->bus),
			dev->bus->number,
			PCI_SLOT(dev->devfn),
			PCI_FUNC(dev->devfn));
		return -EINVAL;
	}


	/* Client already owns the device */
	if (dpriv->client_id == client->client_id) {
		if (dev_entry)
			*dev_entry = dpriv;
		return OK;
	}

	/* Another client owns the device */
	if (is_valid_client_id(dpriv->client_id)) {
		cl_error(
			"invalid client for dev %04x:%02x:%02x.%x, expected %u\n",
			pci_domain_nr(dev->bus),
			dev->bus->number,
			PCI_SLOT(dev->devfn),
			PCI_FUNC(dev->devfn),
			dpriv->client_id);
		return -EBUSY;
	}

	err = pci_enable_device(dev);
	if (unlikely(err)) {
		cl_error("failed to enable dev %04x:%02x:%02x.%x\n",
			 pci_domain_nr(dev->bus),
			 dev->bus->number,
			 PCI_SLOT(dev->devfn),
			 PCI_FUNC(dev->devfn));
		return err;
	}

	cl_info("enabled dev %04x:%02x:%02x.%x\n",
		pci_domain_nr(dev->bus),
		dev->bus->number,
		PCI_SLOT(dev->devfn),
		PCI_FUNC(dev->devfn));

	dpriv->client_id        = client->client_id;
	list_add(&dpriv->list, &client->enabled_devices);
#ifdef MODS_HAS_REINIT_COMPLETION
	reinit_completion(&dpriv->client_completion);
#else
	INIT_COMPLETION(dpriv->client_completion);
#endif

	if (dev_entry)
		*dev_entry = dpriv;
	return OK;
}

void mods_disable_device(struct mods_client *client,
			 struct pci_dev     *dev)
{
	struct en_dev_entry *dpriv = pci_get_drvdata(dev);

	WARN_ON(!mutex_is_locked(&irq_mtx));

	pci_disable_device(dev);

	if (dpriv) {
		dpriv->client_id = INVALID_CLIENT_ID;
		complete(&dpriv->client_completion);
	}

	cl_info("disabled dev %04x:%02x:%02x.%x\n",
		pci_domain_nr(dev->bus),
		dev->bus->number,
		PCI_SLOT(dev->devfn),
		PCI_FUNC(dev->devfn));
}
#endif

static unsigned int get_cur_time(void)
{
	/* This is not very precise, sched_clock() would be better */
	return jiffies_to_usecs(jiffies);
}

static u64 irq_reg_read(const struct irq_mask_info *m, void __iomem *reg)
{
	if (m->mask_type == MODS_MASK_TYPE_IRQ_DISABLE64)
		return readq(reg);
	else
		return readl(reg);
}

static void irq_reg_write(const struct irq_mask_info *m,
			  u64                         value,
			  void               __iomem *reg)
{
	if (m->mask_type == MODS_MASK_TYPE_IRQ_DISABLE64)
		writeq(value, reg);
	else {
		const u32 val32 = (value > ~0U) ? 0 : value;

		writel(val32, reg);
	}
}

static u64 read_irq_state(const struct irq_mask_info *m)
{
	return irq_reg_read(m, m->dev_irq_state);
}

static u64 read_irq_mask(const struct irq_mask_info *m)
{
	return irq_reg_read(m, m->dev_irq_mask_reg);
}

static void write_irq_disable(u64 value, const struct irq_mask_info *m)
{
	irq_reg_write(m, value, m->dev_irq_disable_reg);
}

static int mods_check_interrupt(struct dev_irq_map *t)
{
	int ii    = 0;
	int valid = 0;

	/* For MSI - we always treat it as pending (must rearm later). */
	/* For non-GPU devices - we can't tell. */
	if (t->mask_info_cnt == 0)
		return true;

	for (ii = 0; ii < t->mask_info_cnt; ii++) {
		const struct irq_mask_info *const m = &t->mask_info[ii];

		if (!m->dev_irq_state || !m->dev_irq_mask_reg)
			continue;

		/* GPU device */
		valid |= (read_irq_state(m) && read_irq_mask(m)) != 0;
	}

	return valid;
}

static void mods_disable_interrupts(struct dev_irq_map *t)
{
	u32 ii = 0;

	for (ii = 0; ii < t->mask_info_cnt; ii++) {
		const struct irq_mask_info *const m = &t->mask_info[ii];
		u64 cur_mask = 0;

		if (!m->dev_irq_disable_reg)
			continue;

		if (m->irq_and_mask == 0) {
			write_irq_disable(m->irq_or_mask, m);
			continue;
		}

		cur_mask = read_irq_mask(m);
		cur_mask &= m->irq_and_mask;
		cur_mask |= m->irq_or_mask;
		write_irq_disable(cur_mask, m);
	}

	if ((ii == 0) && t->type == MODS_IRQ_TYPE_CPU) {
		mods_debug_printk(DEBUG_ISR,
				  "disable_irq_nosync %u",
				  t->apic_irq);
		disable_irq_nosync(t->apic_irq);
	}
}

#ifdef CONFIG_PCI
static const char *mods_irq_type_name(u8 irq_type)
{
	switch (irq_type) {
	case MODS_IRQ_TYPE_INT:
		return "INTx";
	case MODS_IRQ_TYPE_MSI:
		return "MSI";
	case MODS_IRQ_TYPE_CPU:
		return "CPU";
	case MODS_IRQ_TYPE_MSIX:
		return "MSI-X";
	default:
		return "unknown";
	}
}
#endif

static void wake_up_client(struct dev_irq_map *t)
{
	struct mods_client *client = mods_client_from_id(t->client_id);

	if (client)
		wake_up_interruptible(&client->interrupt_event);
}

static int rec_irq_done(struct mods_client *client,
			struct dev_irq_map *t,
			unsigned int        irq_time)
{
	/* Get interrupt queue */
	struct irq_q_info *q = &client->irq_queue;

	/* Don't do anything if the IRQ has already been recorded */
	if (q->head != q->tail) {
		unsigned int i;

		for (i = q->head; i != q->tail; i++) {
			struct irq_q_data *pd
					= q->data+(i & (MODS_MAX_IRQS - 1));

			if ((pd->irq == t->apic_irq) &&
			    (!t->dev || (pd->dev == t->dev)))
				return false;
		}
	}

	/* Print an error if the queue is full */
	/* This is deadly! */
	if (q->tail - q->head == MODS_MAX_IRQS) {
		mods_error_printk("IRQ queue is full\n");
		return false;
	}

	/* Record the device which generated the IRQ in the queue */
	q->data[q->tail & (MODS_MAX_IRQS - 1)].dev       = t->dev;
	q->data[q->tail & (MODS_MAX_IRQS - 1)].irq       = t->apic_irq;
	q->data[q->tail & (MODS_MAX_IRQS - 1)].irq_index = t->entry;
	q->data[q->tail & (MODS_MAX_IRQS - 1)].time      = irq_time;
	q->tail++;

#ifdef CONFIG_PCI
	if (t->dev) {
		mods_debug_printk(DEBUG_ISR_DETAILED,
				  "dev %04x:%02x:%02x.%x %s IRQ 0x%x time=%uus\n",
				  pci_domain_nr(t->dev->bus),
				  t->dev->bus->number,
				  PCI_SLOT(t->dev->devfn),
				  PCI_FUNC(t->dev->devfn),
				  mods_irq_type_name(t->type),
				  t->apic_irq,
				  irq_time);
	} else
#endif
		mods_debug_printk(DEBUG_ISR_DETAILED,
				  "CPU IRQ 0x%x, time=%uus\n",
				  t->apic_irq,
				  irq_time);

	return true;
}

/* mods_irq_handle - interrupt function */
static irqreturn_t mods_irq_handle(int irq, void *data)
{
	struct dev_irq_map *t        = (struct dev_irq_map *)data;
	int                 serviced = false;

	if (irq < 0)
		return IRQ_RETVAL(false);

	if (unlikely(!t))
		mods_error_printk("received irq %d, but no context for it\n",
			irq);
	else if (unlikely(t->apic_irq != irq))
		mods_error_printk("received irq %d which doesn't match registered irq %d\n",
			irq, t->apic_irq);
	else {
		unsigned long       flags    = 0;
		int                 recorded = false;
		unsigned int        irq_time = get_cur_time();
		struct mods_client *client   = mods_client_from_id(t->client_id);

		spin_lock_irqsave(&client->irq_lock, flags);

		/* Check if the interrupt is still pending (shared INTA) */
		if (mods_check_interrupt(t)) {

			/* Disable interrupts on this device to avoid interrupt
			 * storm
			 */
			mods_disable_interrupts(t);

			/* Record IRQ for MODS and wake MODS up */
			recorded = rec_irq_done(client, t, irq_time);

			serviced = true;
		}

		spin_unlock_irqrestore(&client->irq_lock, flags);

		if (recorded)
			wake_up_client(t);
	}

	return IRQ_RETVAL(serviced);
}

static int mods_lookup_cpu_irq(u8 client_id, unsigned int irq)
{
	u8  client_idx;
	int ret = IRQ_NOT_FOUND;

	LOG_ENT();

	for (client_idx = 1; client_idx <= MODS_MAX_CLIENTS; client_idx++) {
		struct dev_irq_map *t    = NULL;
		struct dev_irq_map *next = NULL;

		if (!mods_is_client_enabled(client_idx))
			continue;

		list_for_each_entry_safe(t,
					 next,
					 &mods_client_from_id(client_idx)->irq_list,
					 list) {

			if (t->apic_irq == irq) {
				ret = (!client_id || client_id == client_idx)
				      ? IRQ_FOUND : IRQ_NOT_FOUND;

				/* Break out of the outer loop */
				client_idx = MODS_MAX_CLIENTS;
				break;
			}
		}
	}

	LOG_EXT();
	return ret;
}

#ifdef CONFIG_PCI
static int is_nvidia_gpu(struct pci_dev *dev)
{
	unsigned short class_code, vendor_id;

	pci_read_config_word(dev, PCI_CLASS_DEVICE, &class_code);
	pci_read_config_word(dev, PCI_VENDOR_ID, &vendor_id);
	if (((class_code == PCI_CLASS_DISPLAY_VGA) ||
	    (class_code == PCI_CLASS_DISPLAY_3D)) && (vendor_id == 0x10DE)) {
		return true;
	}
	return false;
}

static void setup_mask_info(struct dev_irq_map *newmap,
			    struct MODS_REGISTER_IRQ_4 *p,
			    struct pci_dev *dev)
{
	/* account for legacy adapters */
	u8 __iomem *bar = newmap->dev_irq_aperture;
	u32         ii  = 0;

	if ((p->mask_info_cnt == 0) && is_nvidia_gpu(dev)) {
		struct irq_mask_info *const m = &newmap->mask_info[0];

		newmap->mask_info_cnt = 1;
		m->dev_irq_mask_reg    = (void __iomem *)(bar + 0x140);
		m->dev_irq_disable_reg = (void __iomem *)(bar + 0x140);
		m->dev_irq_state       = (void __iomem *)(bar + 0x100);
		m->irq_and_mask        = 0;
		m->irq_or_mask         = 0;
		return;
	}

	/* setup for new adapters */
	newmap->mask_info_cnt = p->mask_info_cnt;
	for (ii = 0; ii < p->mask_info_cnt; ii++) {
		struct irq_mask_info         *const m = &newmap->mask_info[ii];
		const struct mods_mask_info2 *const in_m = &p->mask_info[ii];

		const u32 pend_offs = in_m->irq_pending_offset;
		const u32 stat_offs = in_m->irq_enabled_offset;
		const u32 dis_offs  = in_m->irq_disable_offset;

		m->dev_irq_state       = (void __iomem *)(bar + pend_offs);
		m->dev_irq_mask_reg    = (void __iomem *)(bar + stat_offs);
		m->dev_irq_disable_reg = (void __iomem *)(bar + dis_offs);
		m->irq_and_mask        = in_m->and_mask;
		m->irq_or_mask         = in_m->or_mask;
		m->mask_type           = in_m->mask_type;
	}
}
#endif

static int add_irq_map(struct mods_client         *client,
		       struct pci_dev             *dev,
		       struct MODS_REGISTER_IRQ_4 *p,
		       u32                         irq,
		       u32                         entry)
{
	struct dev_irq_map *newmap     = NULL;
	u64                 valid_mask = IRQF_TRIGGER_NONE;
	u64                 irq_flags  = MODS_IRQ_FLAG_FROM_FLAGS(p->irq_flags);
	u32                 irq_type   = MODS_IRQ_TYPE_FROM_FLAGS(p->irq_flags);

	LOG_ENT();

	/* Get the flags based on the interrupt type*/
	switch (irq_type) {
	case MODS_IRQ_TYPE_INT:
		irq_flags = IRQF_SHARED;
		break;

	case MODS_IRQ_TYPE_CPU:
		valid_mask = IRQF_TRIGGER_RISING |
			     IRQF_TRIGGER_FALLING |
			     IRQF_SHARED;

		/* Either use a valid flag bit or no flags */
		if (irq_flags & ~valid_mask) {
			cl_error("invalid device interrupt flag %llx\n",
				 (long long)irq_flags);
			return -EINVAL;
		}
		break;

	default:
		irq_flags = IRQF_TRIGGER_NONE;
		break;
	}

	/* Allocate memory for the new entry */
	newmap = kzalloc(sizeof(*newmap), GFP_KERNEL | __GFP_NORETRY);
	if (unlikely(!newmap)) {
		LOG_EXT();
		return -ENOMEM;
	}
	atomic_inc(&client->num_allocs);

	/* Fill out the new entry */
	newmap->apic_irq         = irq;
	newmap->dev              = dev;
	newmap->client_id        = client->client_id;
	newmap->dev_irq_aperture = NULL;
	newmap->mask_info_cnt    = 0;
	newmap->type             = irq_type;
	newmap->entry            = entry;

	/* Enable IRQ for this device in the kernel */
	if (request_irq(irq,
			&mods_irq_handle,
			irq_flags,
			"nvidia mods",
			newmap)) {
		cl_error("unable to enable IRQ 0x%x with flags %llx\n",
			 irq,
			 (long long)irq_flags);
		kfree(newmap);
		atomic_dec(&client->num_allocs);
		LOG_EXT();
		return -EPERM;
	}

	/* Add the new entry to the list of all registered interrupts */
	list_add(&newmap->list, &client->irq_list);

#ifdef CONFIG_PCI
	/* Map BAR0 to be able to disable interrupts */
	if ((irq_type == MODS_IRQ_TYPE_INT) &&
	    (p->aperture_addr != 0) &&
	    (p->aperture_size != 0)) {
		u8 __iomem *bar = ioremap(p->aperture_addr, p->aperture_size);

		if (!bar) {
			cl_debug(DEBUG_ISR,
				 "failed to remap aperture: 0x%llx size=0x%x\n",
				 p->aperture_addr,
				 p->aperture_size);
			LOG_EXT();
			return -EPERM;
		}

		newmap->dev_irq_aperture = bar;
		setup_mask_info(newmap, p, dev);
	}

	if (dev)
		pci_dev_get(dev);
#endif

	/* Print out successful registration string */
	if (irq_type == MODS_IRQ_TYPE_CPU)
		cl_debug(DEBUG_ISR,
			 "registered CPU IRQ 0x%x with flags %llx\n",
			 irq,
			 (long long)irq_flags);
#ifdef CONFIG_PCI
	else if ((irq_type == MODS_IRQ_TYPE_INT) ||
		 (irq_type == MODS_IRQ_TYPE_MSI) ||
		 (irq_type == MODS_IRQ_TYPE_MSIX)) {
		int dom = dev ? pci_domain_nr(dev->bus) : 0;

		if (dom < 0)
			dom = 0;

		cl_debug(DEBUG_ISR,
			 "dev %04x:%02x:%02x.%x registered %s IRQ 0x%x\n",
			 dom,
			 dev ? dev->bus->number : 0U,
			 dev ? PCI_SLOT(dev->devfn) : 0U,
			 dev ? PCI_FUNC(dev->devfn) : 0U,
			 mods_irq_type_name(irq_type),
			 irq);
	}
#endif
#ifdef CONFIG_PCI_MSI
	else if (irq_type == MODS_IRQ_TYPE_MSI) {
		u16 control;
		u16 data;
		int cap_pos = pci_find_capability(dev, PCI_CAP_ID_MSI);

		pci_read_config_word(dev, MSI_CONTROL_REG(cap_pos), &control);
		if (IS_64BIT_ADDRESS(control))
			pci_read_config_word(dev,
						 MSI_DATA_REG(cap_pos, 1),
						 &data);
		else
			pci_read_config_word(dev,
						 MSI_DATA_REG(cap_pos, 0),
						 &data);
		cl_debug(DEBUG_ISR,
			 "dev %04x:%02x:%02x.%x registered MSI IRQ 0x%x data:0x%02x\n",
			 pci_domain_nr(dev->bus),
			 dev->bus->number,
			 PCI_SLOT(dev->devfn),
			 PCI_FUNC(dev->devfn),
			 irq,
			 data);
	} else if (irq_type == MODS_IRQ_TYPE_MSIX) {
		cl_debug(DEBUG_ISR,
			 "dev %04x:%02x:%02x.%x registered MSI-X IRQ 0x%x\n",
			 pci_domain_nr(dev->bus),
			 dev->bus->number,
			 PCI_SLOT(dev->devfn),
			 PCI_FUNC(dev->devfn),
			 irq);
	}
#endif

	LOG_EXT();
	return OK;
}

static void mods_free_map(struct mods_client *client,
			  struct dev_irq_map *del)
{
	unsigned long       flags  = 0;

	LOG_ENT();

	WARN_ON(client->client_id != del->client_id);

	/* Disable interrupts on the device */
	spin_lock_irqsave(&client->irq_lock, flags);
	mods_disable_interrupts(del);
	spin_unlock_irqrestore(&client->irq_lock, flags);

	/* Unhook interrupts in the kernel */
	free_irq(del->apic_irq, del);

	/* Unmap aperture used for masking irqs */
	if (del->dev_irq_aperture)
		iounmap(del->dev_irq_aperture);

#ifdef CONFIG_PCI
	pci_dev_put(del->dev);
#endif

	/* Free memory */
	kfree(del);
	atomic_dec(&client->num_allocs);

	LOG_EXT();
}

void mods_init_irq(void)
{
	LOG_ENT();

	mutex_init(&irq_mtx);

	LOG_EXT();
}

POLL_TYPE mods_irq_event_check(u8 client_id)
{
	struct irq_q_info *q;

	if (!mods_is_client_enabled(client_id))
		return POLLERR; /* client has quit */

	q = &mods_client_from_id(client_id)->irq_queue;

	if (q->head != q->tail)
		return POLLIN; /* irq generated */

	return 0;
}

static int mods_free_irqs(struct mods_client *client,
			  struct pci_dev     *dev)
{
#ifdef CONFIG_PCI
	struct dev_irq_map  *del    = NULL;
	struct dev_irq_map  *next;
	struct en_dev_entry *dpriv;
	unsigned int         irq_type;

	LOG_ENT();

	if (unlikely(mutex_lock_interruptible(&irq_mtx))) {
		LOG_EXT();
		return -EINTR;
	}

	dpriv = pci_get_drvdata(dev);

	if (!dpriv) {
		cl_error(
			"driver data is not set for %04x:%02x:%02x.%x\n",
			pci_domain_nr(dev->bus),
			dev->bus->number,
			PCI_SLOT(dev->devfn),
			PCI_FUNC(dev->devfn));
		mutex_unlock(&irq_mtx);
		LOG_EXT();
		return -EINVAL;
	}

	if (!is_valid_client_id(dpriv->client_id)) {
		cl_error(
			"no client owns dev %04x:%02x:%02x.%x\n",
			pci_domain_nr(dev->bus),
			dev->bus->number,
			PCI_SLOT(dev->devfn),
			PCI_FUNC(dev->devfn));
		mutex_unlock(&irq_mtx);
		LOG_EXT();
		return -EINVAL;
	}

	if (dpriv->client_id != client->client_id) {
		cl_error(
			"invalid client for dev %04x:%02x:%02x.%x, expected %u\n",
			pci_domain_nr(dev->bus),
			dev->bus->number,
			PCI_SLOT(dev->devfn),
			PCI_FUNC(dev->devfn),
			dpriv->client_id);
		mutex_unlock(&irq_mtx);
		LOG_EXT();
		return -EINVAL;
	}

	cl_debug(DEBUG_ISR_DETAILED,
		 "free IRQ for dev %04x:%02x:%02x.%x irq_flags=0x%x nvecs=%d\n",
		 pci_domain_nr(dev->bus),
		 dev->bus->number,
		 PCI_SLOT(dev->devfn),
		 PCI_FUNC(dev->devfn),
		 dpriv->irq_flags,
		 dpriv->nvecs);

	/* Delete device interrupts from the list */
	list_for_each_entry_safe(del, next, &client->irq_list, list) {
		if (dev == del->dev) {
			u8 type = del->type;

			list_del(&del->list);
			cl_debug(DEBUG_ISR,
				 "unregistered %s IRQ 0x%x dev %04x:%02x:%02x.%x\n",
				 mods_irq_type_name(type),
				 del->apic_irq,
				 pci_domain_nr(dev->bus),
				 dev->bus->number,
				 PCI_SLOT(dev->devfn),
				 PCI_FUNC(dev->devfn));
			mods_free_map(client, del);

			WARN_ON(MODS_IRQ_TYPE_FROM_FLAGS(dpriv->irq_flags)
				!= type);
			if (type != MODS_IRQ_TYPE_MSIX)
				break;
		}
	}

	cl_debug(DEBUG_ISR_DETAILED, "before disable\n");
#ifdef CONFIG_PCI_MSI
	irq_type = MODS_IRQ_TYPE_FROM_FLAGS(dpriv->irq_flags);

	if (irq_type == MODS_IRQ_TYPE_MSIX) {
		pci_disable_msix(dev);
		kfree(dpriv->msix_entries);
		if (dpriv->msix_entries)
			atomic_dec(&client->num_allocs);
		dpriv->msix_entries = NULL;
	} else if (irq_type == MODS_IRQ_TYPE_MSI) {
		pci_disable_msi(dev);
	}
#endif

	dpriv->nvecs = 0;
	cl_debug(DEBUG_ISR_DETAILED, "irqs freed\n");
#endif

	mutex_unlock(&irq_mtx);
	LOG_EXT();
	return 0;
}

void mods_free_client_interrupts(struct mods_client *client)
{
	struct list_head *head = &client->enabled_devices;
	struct list_head *iter;
	struct en_dev_entry *dpriv;

	LOG_ENT();

	/* Release all interrupts */
	list_for_each(iter, head) {
		dpriv = list_entry(iter, struct en_dev_entry, list);
		mods_free_irqs(client, dpriv->dev);
	}

	LOG_EXT();
}

#ifdef CONFIG_PCI
static int mods_allocate_irqs(struct mods_client *client,
			      struct pci_dev     *dev,
			      u32                 nvecs,
			      u32                 flags)
{
	struct en_dev_entry *dpriv;
	unsigned int         irq_type = MODS_IRQ_TYPE_FROM_FLAGS(flags);
	int                  err      = OK;

	LOG_ENT();

	cl_debug(DEBUG_ISR_DETAILED,
		 "allocate %u IRQs on dev %04x:%02x:%02x.%x, flags=0x%x\n",
		 nvecs,
		 pci_domain_nr(dev->bus),
		 dev->bus->number,
		 PCI_SLOT(dev->devfn),
		 PCI_FUNC(dev->devfn),
		 flags);

	/* Determine if the device supports requested interrupt type */
#ifdef CONFIG_PCI_MSI
	if (irq_type == MODS_IRQ_TYPE_MSI) {
		if (pci_find_capability(dev, PCI_CAP_ID_MSI) == 0) {
			cl_error("dev %04x:%02x:%02x.%x does not support MSI\n",
				 pci_domain_nr(dev->bus),
				 dev->bus->number,
				 PCI_SLOT(dev->devfn),
				 PCI_FUNC(dev->devfn));
			LOG_EXT();
			return -ENOENT;
		}
	} else if (irq_type == MODS_IRQ_TYPE_MSIX) {
#ifdef MODS_HAS_MSIX_RANGE
		int cnt;
#endif

		if (pci_find_capability(dev, PCI_CAP_ID_MSIX) == 0) {
			cl_error(
				"dev %04x:%02x:%02x.%x does not support MSI-X\n",
				pci_domain_nr(dev->bus),
				dev->bus->number,
				PCI_SLOT(dev->devfn),
				PCI_FUNC(dev->devfn));
			LOG_EXT();
			return -ENOENT;
		}

#ifdef MODS_HAS_MSIX_RANGE
		cnt = pci_msix_vec_count(dev);
		if (cnt < 0) {
			cl_error("MSI-X is not available on dev %04x:%02x:%02x.%x\n",
				 pci_domain_nr(dev->bus),
				 dev->bus->number,
				 PCI_SLOT(dev->devfn),
				 PCI_FUNC(dev->devfn));
			LOG_EXT();
			return cnt;
		}
		if (nvecs > (u32)cnt) {
			cl_error("cannot allocate %u MSI-X vectors on dev %04x:%02x:%02x.%x, only %d supported\n",
				 nvecs,
				 pci_domain_nr(dev->bus),
				 dev->bus->number,
				 PCI_SLOT(dev->devfn),
				 PCI_FUNC(dev->devfn),
				 cnt);
			LOG_EXT();
			return -EINVAL;
		}
#endif
	}
#else
	if (irq_type == MODS_IRQ_TYPE_MSI || irq_type == MODS_IRQ_TYPE_MSIX) {
		cl_error("the kernel does not support MSI\n");
		LOG_EXT();
		return -EINVAL;
	}
#endif

	/* Enable device on the PCI bus */
	err = mods_enable_device(client, dev, &dpriv);
	if (err) {
		LOG_EXT();
		return err;
	}

	if (irq_type == MODS_IRQ_TYPE_INT) {
		/* use legacy irq */
		if (nvecs != 1) {
			cl_error(
				"INTA: only 1 INTA vector supported, requested %u\n",
				nvecs);
			LOG_EXT();
			return -EINVAL;
		}
		dpriv->nvecs = 1;
	}
	/* Enable MSI */
#ifdef CONFIG_PCI_MSI
	else if (irq_type == MODS_IRQ_TYPE_MSI) {
		if (nvecs != 1) {
			cl_error(
				"MSI: only 1 MSI vector supported, requested %u\n",
				nvecs);
			LOG_EXT();
			return -EINVAL;
		}
		err = pci_enable_msi(dev);
		if (err) {
			cl_error(
				"unable to enable MSI on dev %04x:%02x:%02x.%x\n",
				pci_domain_nr(dev->bus),
				dev->bus->number,
				PCI_SLOT(dev->devfn),
				PCI_FUNC(dev->devfn));
			LOG_EXT();
			return err;
		}
		dpriv->nvecs = 1;
	} else if (irq_type == MODS_IRQ_TYPE_MSIX) {
		struct msix_entry *entries;
		int i;
		int cnt;

		entries = kcalloc(nvecs, sizeof(struct msix_entry),
				GFP_KERNEL | __GFP_NORETRY);

		if (!entries) {
			cl_error("could not allocate memory\n");
			LOG_EXT();
			return -ENOMEM;
		}
		atomic_inc(&client->num_allocs);

		for (i = 0; i < nvecs; i++)
			entries[i].entry = (uint16_t)i;

#ifdef MODS_HAS_MSIX_RANGE
		cnt = pci_enable_msix_range(dev, entries, nvecs, nvecs);

		if (cnt < 0) {
			cl_error("failed to allocate %u MSI-X vectors on dev %04x:%02x:%02x.%x\n",
				 nvecs,
				 pci_domain_nr(dev->bus),
				 dev->bus->number,
				 PCI_SLOT(dev->devfn),
				 PCI_FUNC(dev->devfn));
			kfree(entries);
			atomic_dec(&client->num_allocs);
			LOG_EXT();
			return cnt;
		}

		nvecs = (u32)cnt;
#else
		cnt = pci_enable_msix(dev, entries, nvecs);

		if (cnt) {
			/*  A return of < 0 indicates a failure.
			 *  A return of > 0 indicates that driver request is
			 *  exceeding the number of irqs or MSI-X
			 *  vectors available
			 */
			cl_error("failed to allocate %u MSI-X vectors on dev %04x:%02x:%02x.%x\n",
				 nvecs,
				 pci_domain_nr(dev->bus),
				 dev->bus->number,
				 PCI_SLOT(dev->devfn),
				 PCI_FUNC(dev->devfn));
			kfree(entries);
			atomic_dec(&client->num_allocs);
			LOG_EXT();
			if (cnt > 0)
				cnt = -ENOSPC;
			return cnt;
		}
#endif

		cl_debug(DEBUG_ISR,
			 "allocated %d irq's of type %s(%d)\n",
			 nvecs,
			 mods_irq_type_name(irq_type),
			 irq_type);

		for (i = 0; i < nvecs; i++)
			cl_debug(DEBUG_ISR,
				 "vec %d %x\n",
				 entries[i].entry,
				 entries[i].vector);

		dpriv->nvecs = nvecs;
		dpriv->msix_entries = entries;
	}
#endif
	else {
		cl_error("unsupported irq_type %u dev %04x:%02x:%02x.%x\n",
			 irq_type,
			 pci_domain_nr(dev->bus),
			 dev->bus->number,
			 PCI_SLOT(dev->devfn),
			 PCI_FUNC(dev->devfn));
		LOG_EXT();
		return -EINVAL;
	}

	WARN_ON(dpriv->client_id != client->client_id);
	dpriv->irq_flags = flags;
	LOG_EXT();
	return OK;
}

static int mods_register_pci_irq(struct mods_client         *client,
				 struct MODS_REGISTER_IRQ_4 *p)
{
	int                  err = OK;
	unsigned int         irq_type = MODS_IRQ_TYPE_FROM_FLAGS(p->irq_flags);
	struct pci_dev      *dev;
	struct en_dev_entry *dpriv;
	int                  i;

	LOG_ENT();

	if (unlikely(!p->irq_count)) {
		cl_error("no irq's requested\n");
		LOG_EXT();
		return -EINVAL;
	}

	/* Get the PCI device structure for the specified device from kernel */
	err = mods_find_pci_dev(client, &p->dev, &dev);
	if (unlikely(err)) {
		if (err == -ENODEV)
			cl_error("dev %04x:%02x:%02x.%x not found\n",
				 p->dev.domain,
				 p->dev.bus,
				 p->dev.device,
				 p->dev.function);
		LOG_EXT();
		return err;
	}

	if (unlikely(mutex_lock_interruptible(&irq_mtx))) {
		pci_dev_put(dev);
		LOG_EXT();
		return -EINTR;
	}

	dpriv = pci_get_drvdata(dev);
	if (!dpriv) {
		cl_error(
			"driver data is not set for %04x:%02x:%02x.%x\n",
			pci_domain_nr(dev->bus),
			dev->bus->number,
			PCI_SLOT(dev->devfn),
			PCI_FUNC(dev->devfn));
		mutex_unlock(&irq_mtx);
		pci_dev_put(dev);
		LOG_EXT();
		return -EINVAL;
	}

	if (is_valid_client_id(dpriv->client_id)) {
		if (dpriv->client_id != client->client_id) {
			cl_error(
				"dev %04x:%02x:%02x.%x already owned by client %u\n",
				p->dev.domain,
				p->dev.bus,
				p->dev.device,
				p->dev.function,
				dpriv->client_id);
			mutex_unlock(&irq_mtx);
			pci_dev_put(dev);
			LOG_EXT();
			return -EBUSY;
		}
		if (dpriv->nvecs) {
			cl_error(
				"interrupt for dev %04x:%02x:%02x.%x already registered\n",
				p->dev.domain,
				p->dev.bus,
				p->dev.device,
				p->dev.function);
			mutex_unlock(&irq_mtx);
			pci_dev_put(dev);
			LOG_EXT();
			return -EINVAL;
		}
	}

	err = mods_allocate_irqs(client, dev, p->irq_count,
				 p->irq_flags);
	if (err) {
		cl_error("could not allocate irqs for irq_type %d\n",
			 irq_type);
		mutex_unlock(&irq_mtx);
		pci_dev_put(dev);
		LOG_EXT();
		return err;
	}

	dpriv = pci_get_drvdata(dev);

	for (i = 0; i < p->irq_count; i++) {
		u32 irq = ((irq_type == MODS_IRQ_TYPE_INT) ||
				(irq_type == MODS_IRQ_TYPE_MSI)) ? dev->irq :
			   dpriv->msix_entries[i].vector;

		err = add_irq_map(client, dev, p, irq, i);
		if (unlikely(err)) {
#ifdef CONFIG_PCI_MSI
			if (irq_type == MODS_IRQ_TYPE_MSI)
				pci_disable_msi(dev);
			else if (irq_type == MODS_IRQ_TYPE_MSIX)
				pci_disable_msix(dev);
#endif
			break;
		}
	}

	mutex_unlock(&irq_mtx);
	pci_dev_put(dev);
	LOG_EXT();
	return err;
}
#endif /* CONFIG_PCI */

static int mods_register_cpu_irq(struct mods_client         *client,
				 struct MODS_REGISTER_IRQ_4 *p)
{
	u32 irq = p->dev.bus;
	int err;

	LOG_ENT();

	if (unlikely(mutex_lock_interruptible(&irq_mtx))) {
		LOG_EXT();
		return -EINTR;
	}

	/* Determine if the interrupt is already hooked */
	if (mods_lookup_cpu_irq(0, irq) == IRQ_FOUND) {
		cl_error("CPU IRQ 0x%x has already been registered\n", irq);
		mutex_unlock(&irq_mtx);
		LOG_EXT();
		return -EINVAL;
	}

	/* Register interrupt */
	err = add_irq_map(client, NULL, p, irq, 0);

	mutex_unlock(&irq_mtx);
	LOG_EXT();
	return err;
}

#ifdef CONFIG_PCI
static int mods_unregister_pci_irq(struct mods_client         *client,
				   struct MODS_REGISTER_IRQ_2 *p)
{
	struct pci_dev *dev;
	int             err = OK;

	LOG_ENT();

	/* Get the PCI device structure for the specified device from kernel */
	err = mods_find_pci_dev(client, &p->dev, &dev);
	if (unlikely(err)) {
		LOG_EXT();
		return err;
	}

	err = mods_free_irqs(client, dev);

	pci_dev_put(dev);
	LOG_EXT();
	return err;
}
#endif

static int mods_unregister_cpu_irq(struct mods_client *client,
				   struct MODS_REGISTER_IRQ_2 *p)
{
	struct dev_irq_map *del = NULL;
	struct dev_irq_map *next;
	unsigned int        irq;

	LOG_ENT();

	irq = p->dev.bus;

	if (unlikely(mutex_lock_interruptible(&irq_mtx))) {
		LOG_EXT();
		return -EINTR;
	}

	/* Determine if the interrupt is already hooked by this client */
	if (mods_lookup_cpu_irq(client->client_id, irq) == IRQ_NOT_FOUND) {
		cl_error("IRQ 0x%x not hooked, can't unhook\n", irq);
		mutex_unlock(&irq_mtx);
		LOG_EXT();
		return -EINVAL;
	}

	/* Delete device interrupt from the list */
	list_for_each_entry_safe(del, next, &client->irq_list, list) {
		if ((irq == del->apic_irq) && (del->dev == NULL)) {
			if (del->type != p->type) {
				cl_error("wrong IRQ type passed\n");
				mutex_unlock(&irq_mtx);
				LOG_EXT();
				return -EINVAL;
			}
			list_del(&del->list);
			cl_debug(DEBUG_ISR,
				 "unregistered CPU IRQ 0x%x\n",
				 irq);
			mods_free_map(client, del);
			break;
		}
	}

	mutex_unlock(&irq_mtx);
	LOG_EXT();
	return OK;
}

/*************************
 * ESCAPE CALL FUNCTIONS *
 *************************/

int esc_mods_register_irq_4(struct mods_client         *client,
			    struct MODS_REGISTER_IRQ_4 *p)
{
	u32 irq_type = MODS_IRQ_TYPE_FROM_FLAGS(p->irq_flags);

	if (irq_type == MODS_IRQ_TYPE_CPU)
		return mods_register_cpu_irq(client, p);
#ifdef CONFIG_PCI
	return mods_register_pci_irq(client, p);
#else
	cl_error("PCI not available\n");
	return -EINVAL;
#endif
}

int esc_mods_register_irq_3(struct mods_client         *client,
			    struct MODS_REGISTER_IRQ_3 *p)
{
	struct MODS_REGISTER_IRQ_4 irq_data = { {0} };
	u32 ii = 0;

	irq_data.dev = p->dev;
	irq_data.aperture_addr = p->aperture_addr;
	irq_data.aperture_size = p->aperture_size;
	irq_data.mask_info_cnt = p->mask_info_cnt;
	for (ii = 0; ii < p->mask_info_cnt; ii++) {
		irq_data.mask_info[ii].mask_type = p->mask_info[ii].mask_type;
		irq_data.mask_info[ii].irq_pending_offset =
			p->mask_info[ii].irq_pending_offset;
		irq_data.mask_info[ii].irq_enabled_offset =
			p->mask_info[ii].irq_enabled_offset;
		irq_data.mask_info[ii].irq_enable_offset =
			p->mask_info[ii].irq_enable_offset;
		irq_data.mask_info[ii].irq_disable_offset =
			p->mask_info[ii].irq_disable_offset;
		irq_data.mask_info[ii].and_mask =
			p->mask_info[ii].and_mask;
		irq_data.mask_info[ii].or_mask =
			p->mask_info[ii].or_mask;
	}
	irq_data.irq_count = 1;
	irq_data.irq_flags = p->irq_type;

	return esc_mods_register_irq_4(client, &irq_data);
}

int esc_mods_register_irq_2(struct mods_client         *client,
			    struct MODS_REGISTER_IRQ_2 *p)
{
	struct MODS_REGISTER_IRQ_4 irq_data = { {0} };
#ifdef CONFIG_PCI
	struct pci_dev *dev;
	int             err;
	resource_size_t size;
#endif

	irq_data.dev = p->dev;
	irq_data.irq_count = 1;
	irq_data.irq_flags = p->type;

#ifdef CONFIG_PCI
	/* Get the PCI device structure */
	err = mods_find_pci_dev(client, &p->dev, &dev);
	if (unlikely(err))
		return err;

	irq_data.aperture_addr = pci_resource_start(dev, 0);

	size = pci_resource_len(dev, 0);

	pci_dev_put(dev);

	if (size > ~0U) {
		cl_error("BAR0 size of device %04x:%02x:%02x.%x is %llx and exceeds 32 bits\n",
			 p->dev.domain,
			 p->dev.bus,
			 p->dev.device,
			 p->dev.function,
			 (unsigned long long)size);
		return -EINVAL;
	}

	irq_data.aperture_size = (unsigned int)size;
#endif

	return esc_mods_register_irq_4(client, &irq_data);
}

int esc_mods_register_irq(struct mods_client       *client,
			  struct MODS_REGISTER_IRQ *p)
{
	struct MODS_REGISTER_IRQ_2 register_irq = { {0} };

	register_irq.dev.domain		= 0;
	register_irq.dev.bus		= p->dev.bus;
	register_irq.dev.device		= p->dev.device;
	register_irq.dev.function	= p->dev.function;
	register_irq.type		= p->type;

	return esc_mods_register_irq_2(client, &register_irq);
}

int esc_mods_unregister_irq_2(struct mods_client         *client,
			      struct MODS_REGISTER_IRQ_2 *p)
{
	if (p->type == MODS_IRQ_TYPE_CPU)
		return mods_unregister_cpu_irq(client, p);
#ifdef CONFIG_PCI
	return mods_unregister_pci_irq(client, p);
#else
	return -EINVAL;
#endif
}

int esc_mods_unregister_irq(struct mods_client       *client,
			    struct MODS_REGISTER_IRQ *p)
{
	struct MODS_REGISTER_IRQ_2 register_irq = { {0} };

	register_irq.dev.domain   = 0;
	register_irq.dev.bus      = p->dev.bus;
	register_irq.dev.device   = p->dev.device;
	register_irq.dev.function = p->dev.function;
	register_irq.type         = p->type;

	return esc_mods_unregister_irq_2(client, &register_irq);
}

int esc_mods_query_irq_3(struct mods_client      *client,
			 struct MODS_QUERY_IRQ_3 *p)
{
	struct irq_q_info  *q        = NULL;
	int                 err      = OK;
	unsigned int        i        = 0;
	unsigned long       flags    = 0;
	unsigned int        cur_time = get_cur_time();

	LOG_ENT();

	/* Clear return array */
	memset(p->irq_list, 0xFF, sizeof(p->irq_list));

	/* Lock IRQ queue */
	spin_lock_irqsave(&client->irq_lock, flags);

	/* Fill in return array with IRQ information */
	q = &client->irq_queue;
	for (i = 0;
		 (q->head != q->tail) && (i < MODS_MAX_IRQS);
		 q->head++, i++) {

		unsigned int    index = q->head & (MODS_MAX_IRQS - 1);
		struct pci_dev *dev   = q->data[index].dev;

		if (dev) {
			const int dom = pci_domain_nr(dev->bus);

			if (dom < 0 || dom > 0xFFFF) {
				cl_error("unsupported domain %d\n", dom);
				err = -EINVAL;
				goto error;
			}
			p->irq_list[i].dev.domain = dom;
			p->irq_list[i].dev.bus = dev->bus->number;
			p->irq_list[i].dev.device = PCI_SLOT(dev->devfn);
			p->irq_list[i].dev.function = PCI_FUNC(dev->devfn);
		} else {
			if (q->data[index].irq > 0xFFFFU) {
				cl_error("unsupported IRQ %u\n",
					 q->data[index].irq);
				err = -EINVAL;
				goto error;
			}
			p->irq_list[i].dev.domain = 0;
			p->irq_list[i].dev.bus = q->data[index].irq;
			p->irq_list[i].dev.device = 0xFFU;
			p->irq_list[i].dev.function = 0xFFU;
		}
		p->irq_list[i].irq_index = q->data[index].irq_index;
		p->irq_list[i].delay = cur_time - q->data[index].time;

		/* Print info about IRQ status returned */
		if (dev) {
			cl_debug(DEBUG_ISR_DETAILED,
				 "retrieved IRQ index=%d dev %04x:%02x:%02x.%x, time=%uus, delay=%uus\n",
				 p->irq_list[i].irq_index,
				 p->irq_list[i].dev.domain,
				 p->irq_list[i].dev.bus,
				 p->irq_list[i].dev.device,
				 p->irq_list[i].dev.function,
				 q->data[index].time,
				 p->irq_list[i].delay);
		} else {
			cl_debug(DEBUG_ISR_DETAILED,
				 "retrieved IRQ 0x%x, time=%uus, delay=%uus\n",
				 (unsigned int)p->irq_list[i].dev.bus,
				 q->data[index].time,
				 p->irq_list[i].delay);
		}
	}

	/* Indicate if there are more IRQs pending */
	if (q->head != q->tail)
		p->more = 1;

error:
	/* Unlock IRQ queue */
	spin_unlock_irqrestore(&client->irq_lock, flags);

	LOG_EXT();
	return err;
}

int esc_mods_query_irq_2(struct mods_client      *client,
			 struct MODS_QUERY_IRQ_2 *p)
{
	int retval, i;
	struct MODS_QUERY_IRQ_3 query_irq = { { { { 0 } } } };

	retval = esc_mods_query_irq_3(client, &query_irq);
	if (retval)
		return retval;

	for (i = 0; i < MODS_MAX_IRQS; i++) {
		p->irq_list[i].dev   = query_irq.irq_list[i].dev;
		p->irq_list[i].delay = query_irq.irq_list[i].delay;
	}
	p->more = query_irq.more;
	return OK;
}

int esc_mods_query_irq(struct mods_client    *client,
		       struct MODS_QUERY_IRQ *p)
{
	int retval, i;
	struct MODS_QUERY_IRQ_3 query_irq = { { { { 0 } } } };

	retval = esc_mods_query_irq_3(client, &query_irq);
	if (retval)
		return retval;

	for (i = 0; i < MODS_MAX_IRQS; i++) {
		struct mods_irq_3 *entry = &query_irq.irq_list[i];

		if (entry->dev.device > 0xFFU) {
			cl_error("unsupported device %02x\n",
				 entry->dev.device);
			return -EINVAL;
		}
		if (entry->dev.function > 0xFFU) {
			cl_error("unsupported function %02x\n",
				 entry->dev.function);
			return -EINVAL;
		}
		p->irq_list[i].dev.bus      = entry->dev.bus;
		p->irq_list[i].dev.device   = entry->dev.device;
		p->irq_list[i].dev.function = entry->dev.function;
		p->irq_list[i].delay	    = entry->delay;
	}
	p->more = query_irq.more;
	return OK;
}

int esc_mods_irq_handled_2(struct mods_client         *client,
			   struct MODS_REGISTER_IRQ_2 *p)
{
	unsigned long       flags = 0;
	u32                 irq = p->dev.bus;
	struct dev_irq_map *t = NULL;
	struct dev_irq_map *next = NULL;
	int                 err = -EINVAL;

	if (p->type != MODS_IRQ_TYPE_CPU)
		return -EINVAL;

	LOG_ENT();

	/* Print info */
	cl_debug(DEBUG_ISR_DETAILED, "mark CPU IRQ 0x%x handled\n", irq);

	/* Lock IRQ queue */
	spin_lock_irqsave(&client->irq_lock, flags);

	list_for_each_entry_safe(t, next, &client->irq_list, list) {
		if (t->apic_irq == irq) {
			if (t->type != p->type) {
				cl_error(
					"IRQ type doesn't match registered IRQ\n");
			} else {
				enable_irq(irq);
				err = OK;
			}
			break;
		}
	}

	/* Unlock IRQ queue */
	spin_unlock_irqrestore(&client->irq_lock, flags);

	LOG_EXT();
	return err;
}

int esc_mods_irq_handled(struct mods_client       *client,
			 struct MODS_REGISTER_IRQ *p)
{
	struct MODS_REGISTER_IRQ_2 register_irq = { {0} };

	register_irq.dev.domain   = 0;
	register_irq.dev.bus      = p->dev.bus;
	register_irq.dev.device   = p->dev.device;
	register_irq.dev.function = p->dev.function;
	register_irq.type         = p->type;

	return esc_mods_irq_handled_2(client, &register_irq);
}

#if defined(MODS_HAS_TEGRA) && defined(CONFIG_OF_IRQ) && defined(CONFIG_OF)
int esc_mods_map_irq(struct mods_client  *client,
		     struct MODS_DT_INFO *p)
{
	/* irq parameters */
	struct of_phandle_args oirq;
	/* platform device handle */
	struct platform_device *pdev = NULL;
	struct device_node *np;
	int err = 0;
	/* the physical irq */
	int hwirq;

	/* Make sure strings from userspace are null-terminated */
	p->dt_name[sizeof(p->dt_name) - 1] = 0;
	p->full_name[sizeof(p->full_name) - 1] = 0;

	/* Search for the node by device tree name */
	np = of_find_node_by_name(NULL, p->dt_name);

	if (!np) {
		cl_error("node %s is not valid\n", p->full_name);
		err = -EINVAL;
		goto error;
	}

	/* Can be multiple nodes that share the same dt name, */
	/* make sure you get the correct node matched by the device's full */
	/* name in device tree (i.e. watchdog@30c0000 as opposed */
	/* to watchdog) */
	while (of_node_cmp(np->full_name, p->full_name)) {
		np = of_find_node_by_name(np, p->dt_name);
		if (!np) {
			cl_error("node %s is not valid\n", p->full_name);
			err = -EINVAL;
			goto error;
		}
	}

	if (p->index > 0x7FFFFFFFU) {
		cl_error("unsupported index %u\n", p->index);
		err = -EINVAL;
		goto error;
	}
	p->irq = irq_of_parse_and_map(np, p->index);
	err = of_irq_parse_one(np, p->index, &oirq);
	if (err) {
		cl_error("could not parse IRQ\n");
		goto error;
	}
	if ((int)oirq.args[1] < 0) {
		cl_error("unsupported IRQ %d\n", (int)oirq.args[1]);
		err = -EINVAL;
		goto error;
	}

	hwirq = (int)oirq.args[1];

	/* Get the platform device handle */
	pdev = of_find_device_by_node(np);

	if (of_node_cmp(p->dt_name, "watchdog") == 0) {
		/* Enable and unmask interrupt for watchdog */
		struct resource *res_src =
			platform_get_resource(pdev, IORESOURCE_MEM, 0);
		struct resource *res_tke =
			platform_get_resource(pdev, IORESOURCE_MEM, 2);
		void __iomem    *wdt_tke = NULL;
		unsigned int     wdt_index = 0;

		if (res_tke && res_src) {
			wdt_tke = devm_ioremap(&pdev->dev, res_tke->start,
					       resource_size(res_tke));
			wdt_index = (unsigned int)
				(((res_src->start >> 16) & 0xFU) - 0xCU);
		}

		if (wdt_tke) {
			writel(TOP_TKE_TKEIE_WDT_MASK(wdt_index),
			       wdt_tke + TOP_TKE_TKEIE(hwirq));
		}
	}

error:
	of_node_put(np);
	/* enable the interrupt */
	return err;
}

int esc_mods_map_irq_to_gpio(struct mods_client    *client,
			     struct MODS_GPIO_INFO *p)
{
	//TODO: Make sure you are allocating gpio properly
	struct device_node *np;
	int gpio_handle;
	int irq;
	int err = 0;

	/* Make sure strings from userspace are null-terminated */
	p->dt_name[sizeof(p->dt_name) - 1] = 0;
	p->full_name[sizeof(p->full_name) - 1] = 0;

	np = of_find_node_by_name(NULL, p->dt_name);

	if (!np) {
		cl_error("node %s is not valid\n", p->full_name);
		err = -EINVAL;
		goto error;
	}

	while (of_node_cmp(np->full_name, p->full_name)) {
		np = of_find_node_by_name(np, p->dt_name);
		if (!np) {
			cl_error("node %s is not valid\n", p->full_name);
			err = -EINVAL;
			goto error;
		}
	}

	gpio_handle = of_get_named_gpio(np, p->name, 0);
	if (!gpio_is_valid(gpio_handle)) {
		cl_error("gpio %s is missing\n", p->name);
		err = gpio_handle;
		goto error;
	}

	err = gpio_direction_input(gpio_handle);
	if (err < 0) {
		cl_error("pex_rst_gpio input direction change failed\n");
		goto error;
	}

	irq = gpio_to_irq(gpio_handle);
	if (irq < 0) {
		cl_error("Unable to get irq for pex_rst_gpio\n");
		err = -EINVAL;
		goto error;
	}
	p->irq = irq;

error:
	of_node_put(np);
	return err;
}
#endif
