+/**
+ * @reg: virtual address of interrupt register
+ * @hwirq: hardware irq number
+ * @irq: virtual irq number
+ * @node: list node
+ */
+struct pcs_interrupt {
+ void __iomem *reg;
+ irq_hw_number_t hwirq;
+ unsigned int irq;
+ struct list_head node;
+};
+
+/**
+ * pcs_irq_set() - enables or disables an interrupt
+ *
+ * Note that this currently assumes one interrupt per pinctrl
+ * register that is typically used for wake-up events.
+ */
+static inline void pcs_irq_set(struct pcs_soc_data *pcs_soc,
+ int irq, const bool enable)
+{
+ struct pcs_device *pcs;
+ struct list_head *pos;
+ unsigned mask;
+
+ pcs = container_of(pcs_soc, struct pcs_device, socdata);
+ list_for_each(pos, &pcs->irqs) {
+ struct pcs_interrupt *pcswi;
+ unsigned soc_mask;
+
+ pcswi = list_entry(pos, struct pcs_interrupt, node);
+ if (irq != pcswi->irq)
+ continue;
+
+ soc_mask = pcs_soc->irq_enable_mask;
+ raw_spin_lock(&pcs->lock);
+ mask = pcs->read(pcswi->reg);
+ if (enable)
+ mask |= soc_mask;
+ else
+ mask &= ~soc_mask;
+ pcs->write(mask, pcswi->reg);
+ raw_spin_unlock(&pcs->lock);
+ }
+}
+
+/**
+ * pcs_irq_mask() - mask pinctrl interrupt
+ * @d: interrupt data
+ */
+static void pcs_irq_mask(struct irq_data *d)
+{
+ struct pcs_soc_data *pcs_soc = irq_data_get_irq_chip_data(d);
+
+ pcs_irq_set(pcs_soc, d->irq, false);
+}
+
+/**
+ * pcs_irq_unmask() - unmask pinctrl interrupt
+ * @d: interrupt data
+ */
+static void pcs_irq_unmask(struct irq_data *d)
+{
+ struct pcs_soc_data *pcs_soc = irq_data_get_irq_chip_data(d);
+
+ pcs_irq_set(pcs_soc, d->irq, true);
+}
+
+/**
+ * pcs_irq_set_wake() - toggle the suspend and resume wake up
+ * @d: interrupt data
+ * @state: wake-up state
+ *
+ * Note that this should be called only for suspend and resume.
+ * For runtime PM, the wake-up events should be enabled by default.
+ */
+static int pcs_irq_set_wake(struct irq_data *d, unsigned int state)
+{
+ if (state)
+ pcs_irq_unmask(d);
+ else
+ pcs_irq_mask(d);
+
+ return 0;
+}
+
+/**
+ * pcs_irq_handle() - common interrupt handler
+ * @pcs_irq: interrupt data
+ *
+ * Note that this currently assumes we have one interrupt bit per
+ * mux register. This interrupt is typically used for wake-up events.
+ * For more complex interrupts different handlers can be specified.
+ */
+static int pcs_irq_handle(struct pcs_soc_data *pcs_soc)
+{
+ struct pcs_device *pcs;
+ struct list_head *pos;
+ int count = 0;
+
+ pcs = container_of(pcs_soc, struct pcs_device, socdata);
+ list_for_each(pos, &pcs->irqs) {
+ struct pcs_interrupt *pcswi;
+ unsigned mask;
+
+ pcswi = list_entry(pos, struct pcs_interrupt, node);
+ raw_spin_lock(&pcs->lock);
+ mask = pcs->read(pcswi->reg);
+ raw_spin_unlock(&pcs->lock);
+ if (mask & pcs_soc->irq_status_mask) {
+ generic_handle_irq(irq_find_mapping(pcs->domain,
+ pcswi->hwirq));
+ count++;
+ }
+ }
+
+ return count;
+}
+
+/**
+ * pcs_irq_handler() - handler for the shared interrupt case
+ * @irq: interrupt
+ * @d: data
+ *
+ * Use this for cases where multiple instances of
+ * pinctrl-single share a single interrupt like on omaps.
+ */
+static irqreturn_t pcs_irq_handler(int irq, void *d)
+{
+ struct pcs_soc_data *pcs_soc = d;
+
+ return pcs_irq_handle(pcs_soc) ? IRQ_HANDLED : IRQ_NONE;
+}
+
+/**
+ * pcs_irq_handle() - handler for the dedicated chained interrupt case
+ * @irq: interrupt
+ * @desc: interrupt descriptor
+ *
+ * Use this if you have a separate interrupt for each
+ * pinctrl-single instance.
+ */
+static void pcs_irq_chain_handler(unsigned int irq, struct irq_desc *desc)
+{
+ struct pcs_soc_data *pcs_soc = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip;
+ int res;
+
+ chip = irq_get_chip(irq);
+ chained_irq_enter(chip, desc);
+ res = pcs_irq_handle(pcs_soc);
+ /* REVISIT: export and add handle_bad_irq(irq, desc)? */
+ chained_irq_exit(chip, desc);
+
+ return;
+}
+
+static int pcs_irqdomain_map(struct irq_domain *d, unsigned int irq,
+ irq_hw_number_t hwirq)
+{
+ struct pcs_soc_data *pcs_soc = d->host_data;
+ struct pcs_device *pcs;
+ struct pcs_interrupt *pcswi;
+
+ pcs = container_of(pcs_soc, struct pcs_device, socdata);
+ pcswi = devm_kzalloc(pcs->dev, sizeof(*pcswi), GFP_KERNEL);
+ if (!pcswi)
+ return -ENOMEM;
+
+ pcswi->reg = pcs->base + hwirq;
+ pcswi->hwirq = hwirq;
+ pcswi->irq = irq;
+
+ mutex_lock(&pcs->mutex);
+ list_add_tail(&pcswi->node, &pcs->irqs);
+ mutex_unlock(&pcs->mutex);
+
+ irq_set_chip_data(irq, pcs_soc);
+ irq_set_chip_and_handler(irq, &pcs->chip,
+ handle_level_irq);
+ set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
+
+ return 0;
+}
+
+static struct irq_domain_ops pcs_irqdomain_ops = {
+ .map = pcs_irqdomain_map,
+ .xlate = irq_domain_xlate_onecell,
+};
+
+/**
+ * pcs_irq_init_chained_handler() - set up a chained interrupt handler
+ * @pcs: pcs driver instance
+ * @np: device node pointer
+ */
+static int pcs_irq_init_chained_handler(struct pcs_device *pcs,
+ struct device_node *np)
+{
+ struct pcs_soc_data *pcs_soc = &pcs->socdata;
+ const char *name = "pinctrl";
+ int num_irqs;
+
+ if (!pcs_soc->irq_enable_mask ||
+ !pcs_soc->irq_status_mask) {
+ pcs_soc->irq = -1;
+ return -EINVAL;
+ }
+
+ INIT_LIST_HEAD(&pcs->irqs);
+ pcs->chip.name = name;
+ pcs->chip.irq_ack = pcs_irq_mask;
+ pcs->chip.irq_mask = pcs_irq_mask;
+ pcs->chip.irq_unmask = pcs_irq_unmask;
+ pcs->chip.irq_set_wake = pcs_irq_set_wake;
+
+ if (PCS_QUIRK_HAS_SHARED_IRQ) {
+ int res;
+
+ res = request_irq(pcs_soc->irq, pcs_irq_handler,
+ IRQF_SHARED | IRQF_NO_SUSPEND,
+ name, pcs_soc);
+ if (res) {
+ pcs_soc->irq = -1;
+ return res;
+ }
+ } else {
+ irq_set_handler_data(pcs_soc->irq, pcs_soc);
+ irq_set_chained_handler(pcs_soc->irq,
+ pcs_irq_chain_handler);
+ }
+
+ /*
+ * We can use the register offset as the hardirq
+ * number as irq_domain_add_simple maps them lazily.
+ * This way we can easily support more than one
+ * interrupt per function if needed.
+ */
+ num_irqs = pcs->size;
+
+ pcs->domain = irq_domain_add_simple(np, num_irqs, 0,
+ &pcs_irqdomain_ops,
+ pcs_soc);
+ if (!pcs->domain) {
+ irq_set_chained_handler(pcs_soc->irq, NULL);
+ return -EINVAL;
+ }
+
+ return 0;
+}