#include <linux/errno.h>
 #include <linux/io.h>
 #include <linux/ioport.h>
+#include <linux/interrupt.h>
 #include <linux/isa.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 module_param_hw_array(base, uint, ioport, &num_quad8, 0);
 MODULE_PARM_DESC(base, "ACCES 104-QUAD-8 base addresses");
 
+static unsigned int irq[max_num_isa_dev(QUAD8_EXTENT)];
+module_param_hw_array(irq, uint, irq, NULL, 0);
+MODULE_PARM_DESC(irq, "ACCES 104-QUAD-8 interrupt line numbers");
+
 #define QUAD8_NUM_COUNTERS 8
 
 /**
  * @quadrature_scale:  array of quadrature mode scale configurations
  * @ab_enable:         array of A and B inputs enable configurations
  * @preset_enable:     array of set_to_preset_on_index attribute configurations
+ * @irq_trigger:       array of current IRQ trigger function configurations
+ * @next_irq_trigger:  array of next IRQ trigger function configurations
  * @synchronous_mode:  array of index function synchronous mode configurations
  * @index_polarity:    array of index function polarity configurations
  * @cable_fault_enable:        differential encoder cable status enable configurations
        unsigned int quadrature_scale[QUAD8_NUM_COUNTERS];
        unsigned int ab_enable[QUAD8_NUM_COUNTERS];
        unsigned int preset_enable[QUAD8_NUM_COUNTERS];
+       unsigned int irq_trigger[QUAD8_NUM_COUNTERS];
+       unsigned int next_irq_trigger[QUAD8_NUM_COUNTERS];
        unsigned int synchronous_mode[QUAD8_NUM_COUNTERS];
        unsigned int index_polarity[QUAD8_NUM_COUNTERS];
        unsigned int cable_fault_enable;
        unsigned int base;
 };
 
+#define QUAD8_REG_INTERRUPT_STATUS 0x10
 #define QUAD8_REG_CHAN_OP 0x11
+#define QUAD8_REG_INDEX_INTERRUPT 0x12
 #define QUAD8_REG_INDEX_INPUT_LEVELS 0x16
 #define QUAD8_DIFF_ENCODER_CABLE_STATUS 0x17
 /* Borrow Toggle flip-flop */
 #define QUAD8_RLD_CNTR_OUT 0x10
 /* Transfer Preset Register LSB to FCK Prescaler */
 #define QUAD8_RLD_PRESET_PSC 0x18
-#define QUAD8_CHAN_OP_ENABLE_COUNTERS 0x00
 #define QUAD8_CHAN_OP_RESET_COUNTERS 0x01
+#define QUAD8_CHAN_OP_ENABLE_INTERRUPT_FUNC 0x04
 #define QUAD8_CMR_QUADRATURE_X1 0x08
 #define QUAD8_CMR_QUADRATURE_X2 0x10
 #define QUAD8_CMR_QUADRATURE_X4 0x18
        }
 }
 
+enum {
+       QUAD8_EVENT_NONE = -1,
+       QUAD8_EVENT_CARRY = 0,
+       QUAD8_EVENT_COMPARE = 1,
+       QUAD8_EVENT_CARRY_BORROW = 2,
+       QUAD8_EVENT_INDEX = 3,
+};
+
+static int quad8_events_configure(struct counter_device *counter)
+{
+       struct quad8 *const priv = counter->priv;
+       unsigned long irq_enabled = 0;
+       unsigned long irqflags;
+       size_t channel;
+       unsigned long ior_cfg;
+       unsigned long base_offset;
+
+       spin_lock_irqsave(&priv->lock, irqflags);
+
+       /* Enable interrupts for the requested channels, disable for the rest */
+       for (channel = 0; channel < QUAD8_NUM_COUNTERS; channel++) {
+               if (priv->next_irq_trigger[channel] == QUAD8_EVENT_NONE)
+                       continue;
+
+               if (priv->irq_trigger[channel] != priv->next_irq_trigger[channel]) {
+                       /* Save new IRQ function configuration */
+                       priv->irq_trigger[channel] = priv->next_irq_trigger[channel];
+
+                       /* Load configuration to I/O Control Register */
+                       ior_cfg = priv->ab_enable[channel] |
+                                 priv->preset_enable[channel] << 1 |
+                                 priv->irq_trigger[channel] << 3;
+                       base_offset = priv->base + 2 * channel + 1;
+                       outb(QUAD8_CTR_IOR | ior_cfg, base_offset);
+               }
+
+               /* Reset next IRQ trigger function configuration */
+               priv->next_irq_trigger[channel] = QUAD8_EVENT_NONE;
+
+               /* Enable IRQ line */
+               irq_enabled |= BIT(channel);
+       }
+
+       outb(irq_enabled, priv->base + QUAD8_REG_INDEX_INTERRUPT);
+
+       spin_unlock_irqrestore(&priv->lock, irqflags);
+
+       return 0;
+}
+
+static int quad8_watch_validate(struct counter_device *counter,
+                               const struct counter_watch *watch)
+{
+       struct quad8 *const priv = counter->priv;
+
+       if (watch->channel > QUAD8_NUM_COUNTERS - 1)
+               return -EINVAL;
+
+       switch (watch->event) {
+       case COUNTER_EVENT_OVERFLOW:
+               if (priv->next_irq_trigger[watch->channel] == QUAD8_EVENT_NONE)
+                       priv->next_irq_trigger[watch->channel] = QUAD8_EVENT_CARRY;
+               else if (priv->next_irq_trigger[watch->channel] != QUAD8_EVENT_CARRY)
+                       return -EINVAL;
+               return 0;
+       case COUNTER_EVENT_THRESHOLD:
+               if (priv->next_irq_trigger[watch->channel] == QUAD8_EVENT_NONE)
+                       priv->next_irq_trigger[watch->channel] = QUAD8_EVENT_COMPARE;
+               else if (priv->next_irq_trigger[watch->channel] != QUAD8_EVENT_COMPARE)
+                       return -EINVAL;
+               return 0;
+       case COUNTER_EVENT_OVERFLOW_UNDERFLOW:
+               if (priv->next_irq_trigger[watch->channel] == QUAD8_EVENT_NONE)
+                       priv->next_irq_trigger[watch->channel] = QUAD8_EVENT_CARRY_BORROW;
+               else if (priv->next_irq_trigger[watch->channel] != QUAD8_EVENT_CARRY_BORROW)
+                       return -EINVAL;
+               return 0;
+       case COUNTER_EVENT_INDEX:
+               if (priv->next_irq_trigger[watch->channel] == QUAD8_EVENT_NONE)
+                       priv->next_irq_trigger[watch->channel] = QUAD8_EVENT_INDEX;
+               else if (priv->next_irq_trigger[watch->channel] != QUAD8_EVENT_INDEX)
+                       return -EINVAL;
+               return 0;
+       default:
+               return -EINVAL;
+       }
+}
+
 static const struct counter_ops quad8_ops = {
        .signal_read = quad8_signal_read,
        .count_read = quad8_count_read,
        .count_write = quad8_count_write,
        .function_read = quad8_function_read,
        .function_write = quad8_function_write,
-       .action_read = quad8_action_read
+       .action_read = quad8_action_read,
+       .events_configure = quad8_events_configure,
+       .watch_validate = quad8_watch_validate,
 };
 
 static const char *const quad8_index_polarity_modes[] = {
 
        priv->ab_enable[count->id] = enable;
 
-       ior_cfg = enable | priv->preset_enable[count->id] << 1;
+       ior_cfg = enable | priv->preset_enable[count->id] << 1 |
+                 priv->irq_trigger[count->id] << 3;
 
        /* Load I/O control configuration */
        outb(QUAD8_CTR_IOR | ior_cfg, base_offset + 1);
 
        priv->preset_enable[count->id] = preset_enable;
 
-       ior_cfg = priv->ab_enable[count->id] | preset_enable << 1;
+       ior_cfg = priv->ab_enable[count->id] | preset_enable << 1 |
+                 priv->irq_trigger[count->id] << 3;
 
        /* Load I/O control configuration to Input / Output Control Register */
        outb(QUAD8_CTR_IOR | ior_cfg, base_offset);
        QUAD8_COUNT(7, "Channel 8 Count")
 };
 
+static irqreturn_t quad8_irq_handler(int irq, void *private)
+{
+       struct quad8 *const priv = private;
+       const unsigned long base = priv->base;
+       unsigned long irq_status;
+       unsigned long channel;
+       u8 event;
+
+       irq_status = inb(base + QUAD8_REG_INTERRUPT_STATUS);
+       if (!irq_status)
+               return IRQ_NONE;
+
+       for_each_set_bit(channel, &irq_status, QUAD8_NUM_COUNTERS) {
+               switch (priv->irq_trigger[channel]) {
+               case QUAD8_EVENT_CARRY:
+                       event = COUNTER_EVENT_OVERFLOW;
+                               break;
+               case QUAD8_EVENT_COMPARE:
+                       event = COUNTER_EVENT_THRESHOLD;
+                               break;
+               case QUAD8_EVENT_CARRY_BORROW:
+                       event = COUNTER_EVENT_OVERFLOW_UNDERFLOW;
+                               break;
+               case QUAD8_EVENT_INDEX:
+                       event = COUNTER_EVENT_INDEX;
+                               break;
+               default:
+                       /* should never reach this path */
+                       WARN_ONCE(true, "invalid interrupt trigger function %u configured for channel %lu\n",
+                                 priv->irq_trigger[channel], channel);
+                       continue;
+               }
+
+               counter_push_event(&priv->counter, event, channel);
+       }
+
+       /* Clear pending interrupts on device */
+       outb(QUAD8_CHAN_OP_ENABLE_INTERRUPT_FUNC, base + QUAD8_REG_CHAN_OP);
+
+       return IRQ_HANDLED;
+}
+
 static int quad8_probe(struct device *dev, unsigned int id)
 {
        struct quad8 *priv;
        int i, j;
        unsigned int base_offset;
+       int err;
 
        if (!devm_request_region(dev, base[id], QUAD8_EXTENT, dev_name(dev))) {
                dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n",
 
        spin_lock_init(&priv->lock);
 
+       /* Reset Index/Interrupt Register */
+       outb(0x00, base[id] + QUAD8_REG_INDEX_INTERRUPT);
        /* Reset all counters and disable interrupt function */
        outb(QUAD8_CHAN_OP_RESET_COUNTERS, base[id] + QUAD8_REG_CHAN_OP);
        /* Set initial configuration for all counters */
                outb(QUAD8_CTR_IOR, base_offset + 1);
                /* Disable index function; negative index polarity */
                outb(QUAD8_CTR_IDR, base_offset + 1);
+               /* Initialize next IRQ trigger function configuration */
+               priv->next_irq_trigger[i] = QUAD8_EVENT_NONE;
        }
        /* Disable Differential Encoder Cable Status for all channels */
        outb(0xFF, base[id] + QUAD8_DIFF_ENCODER_CABLE_STATUS);
-       /* Enable all counters */
-       outb(QUAD8_CHAN_OP_ENABLE_COUNTERS, base[id] + QUAD8_REG_CHAN_OP);
+       /* Enable all counters and enable interrupt function */
+       outb(QUAD8_CHAN_OP_ENABLE_INTERRUPT_FUNC, base[id] + QUAD8_REG_CHAN_OP);
+
+       err = devm_request_irq(dev, irq[id], quad8_irq_handler, IRQF_SHARED,
+                              priv->counter.name, priv);
+       if (err)
+               return err;
 
        return devm_counter_register(dev, &priv->counter);
 }