linux/drivers/irqchip/irq-ingenic-tcu.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * JZ47xx SoCs TCU IRQ driver
   4 * Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net>
   5 */
   6
   7#include <linux/clk.h>
   8#include <linux/interrupt.h>
   9#include <linux/irqchip.h>
  10#include <linux/irqchip/chained_irq.h>
  11#include <linux/mfd/ingenic-tcu.h>
  12#include <linux/mfd/syscon.h>
  13#include <linux/of_irq.h>
  14#include <linux/regmap.h>
  15
  16struct ingenic_tcu {
  17        struct regmap *map;
  18        struct clk *clk;
  19        struct irq_domain *domain;
  20        unsigned int nb_parent_irqs;
  21        u32 parent_irqs[3];
  22};
  23
  24static void ingenic_tcu_intc_cascade(struct irq_desc *desc)
  25{
  26        struct irq_chip *irq_chip = irq_data_get_irq_chip(&desc->irq_data);
  27        struct irq_domain *domain = irq_desc_get_handler_data(desc);
  28        struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 0);
  29        struct regmap *map = gc->private;
  30        uint32_t irq_reg, irq_mask;
  31        unsigned int i;
  32
  33        regmap_read(map, TCU_REG_TFR, &irq_reg);
  34        regmap_read(map, TCU_REG_TMR, &irq_mask);
  35
  36        chained_irq_enter(irq_chip, desc);
  37
  38        irq_reg &= ~irq_mask;
  39
  40        for_each_set_bit(i, (unsigned long *)&irq_reg, 32)
  41                generic_handle_domain_irq(domain, i);
  42
  43        chained_irq_exit(irq_chip, desc);
  44}
  45
  46static void ingenic_tcu_gc_unmask_enable_reg(struct irq_data *d)
  47{
  48        struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
  49        struct irq_chip_type *ct = irq_data_get_chip_type(d);
  50        struct regmap *map = gc->private;
  51        u32 mask = d->mask;
  52
  53        irq_gc_lock(gc);
  54        regmap_write(map, ct->regs.ack, mask);
  55        regmap_write(map, ct->regs.enable, mask);
  56        *ct->mask_cache |= mask;
  57        irq_gc_unlock(gc);
  58}
  59
  60static void ingenic_tcu_gc_mask_disable_reg(struct irq_data *d)
  61{
  62        struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
  63        struct irq_chip_type *ct = irq_data_get_chip_type(d);
  64        struct regmap *map = gc->private;
  65        u32 mask = d->mask;
  66
  67        irq_gc_lock(gc);
  68        regmap_write(map, ct->regs.disable, mask);
  69        *ct->mask_cache &= ~mask;
  70        irq_gc_unlock(gc);
  71}
  72
  73static void ingenic_tcu_gc_mask_disable_reg_and_ack(struct irq_data *d)
  74{
  75        struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
  76        struct irq_chip_type *ct = irq_data_get_chip_type(d);
  77        struct regmap *map = gc->private;
  78        u32 mask = d->mask;
  79
  80        irq_gc_lock(gc);
  81        regmap_write(map, ct->regs.ack, mask);
  82        regmap_write(map, ct->regs.disable, mask);
  83        irq_gc_unlock(gc);
  84}
  85
  86static int __init ingenic_tcu_irq_init(struct device_node *np,
  87                                       struct device_node *parent)
  88{
  89        struct irq_chip_generic *gc;
  90        struct irq_chip_type *ct;
  91        struct ingenic_tcu *tcu;
  92        struct regmap *map;
  93        unsigned int i;
  94        int ret, irqs;
  95
  96        map = device_node_to_regmap(np);
  97        if (IS_ERR(map))
  98                return PTR_ERR(map);
  99
 100        tcu = kzalloc(sizeof(*tcu), GFP_KERNEL);
 101        if (!tcu)
 102                return -ENOMEM;
 103
 104        tcu->map = map;
 105
 106        irqs = of_property_count_elems_of_size(np, "interrupts", sizeof(u32));
 107        if (irqs < 0 || irqs > ARRAY_SIZE(tcu->parent_irqs)) {
 108                pr_crit("%s: Invalid 'interrupts' property\n", __func__);
 109                ret = -EINVAL;
 110                goto err_free_tcu;
 111        }
 112
 113        tcu->nb_parent_irqs = irqs;
 114
 115        tcu->domain = irq_domain_add_linear(np, 32, &irq_generic_chip_ops,
 116                                            NULL);
 117        if (!tcu->domain) {
 118                ret = -ENOMEM;
 119                goto err_free_tcu;
 120        }
 121
 122        ret = irq_alloc_domain_generic_chips(tcu->domain, 32, 1, "TCU",
 123                                             handle_level_irq, 0,
 124                                             IRQ_NOPROBE | IRQ_LEVEL, 0);
 125        if (ret) {
 126                pr_crit("%s: Invalid 'interrupts' property\n", __func__);
 127                goto out_domain_remove;
 128        }
 129
 130        gc = irq_get_domain_generic_chip(tcu->domain, 0);
 131        ct = gc->chip_types;
 132
 133        gc->wake_enabled = IRQ_MSK(32);
 134        gc->private = tcu->map;
 135
 136        ct->regs.disable = TCU_REG_TMSR;
 137        ct->regs.enable = TCU_REG_TMCR;
 138        ct->regs.ack = TCU_REG_TFCR;
 139        ct->chip.irq_unmask = ingenic_tcu_gc_unmask_enable_reg;
 140        ct->chip.irq_mask = ingenic_tcu_gc_mask_disable_reg;
 141        ct->chip.irq_mask_ack = ingenic_tcu_gc_mask_disable_reg_and_ack;
 142        ct->chip.flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE;
 143
 144        /* Mask all IRQs by default */
 145        regmap_write(tcu->map, TCU_REG_TMSR, IRQ_MSK(32));
 146
 147        /*
 148         * On JZ4740, timer 0 and timer 1 have their own interrupt line;
 149         * timers 2-7 share one interrupt.
 150         * On SoCs >= JZ4770, timer 5 has its own interrupt line;
 151         * timers 0-4 and 6-7 share one single interrupt.
 152         *
 153         * To keep things simple, we just register the same handler to
 154         * all parent interrupts. The handler will properly detect which
 155         * channel fired the interrupt.
 156         */
 157        for (i = 0; i < irqs; i++) {
 158                tcu->parent_irqs[i] = irq_of_parse_and_map(np, i);
 159                if (!tcu->parent_irqs[i]) {
 160                        ret = -EINVAL;
 161                        goto out_unmap_irqs;
 162                }
 163
 164                irq_set_chained_handler_and_data(tcu->parent_irqs[i],
 165                                                 ingenic_tcu_intc_cascade,
 166                                                 tcu->domain);
 167        }
 168
 169        return 0;
 170
 171out_unmap_irqs:
 172        for (; i > 0; i--)
 173                irq_dispose_mapping(tcu->parent_irqs[i - 1]);
 174out_domain_remove:
 175        irq_domain_remove(tcu->domain);
 176err_free_tcu:
 177        kfree(tcu);
 178        return ret;
 179}
 180IRQCHIP_DECLARE(jz4740_tcu_irq, "ingenic,jz4740-tcu", ingenic_tcu_irq_init);
 181IRQCHIP_DECLARE(jz4725b_tcu_irq, "ingenic,jz4725b-tcu", ingenic_tcu_irq_init);
 182IRQCHIP_DECLARE(jz4760_tcu_irq, "ingenic,jz4760-tcu", ingenic_tcu_irq_init);
 183IRQCHIP_DECLARE(jz4770_tcu_irq, "ingenic,jz4770-tcu", ingenic_tcu_irq_init);
 184IRQCHIP_DECLARE(x1000_tcu_irq, "ingenic,x1000-tcu", ingenic_tcu_irq_init);
 185