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