linux/drivers/irqchip/irq-metag.c
<<
>>
Prefs
   1/*
   2 * Meta internal (HWSTATMETA) interrupt code.
   3 *
   4 * Copyright (C) 2011-2012 Imagination Technologies Ltd.
   5 *
   6 * This code is based on the code in SoC/common/irq.c and SoC/comet/irq.c
   7 * The code base could be generalised/merged as a lot of the functionality is
   8 * similar. Until this is done, we try to keep the code simple here.
   9 */
  10
  11#include <linux/interrupt.h>
  12#include <linux/io.h>
  13#include <linux/irqdomain.h>
  14
  15#include <asm/irq.h>
  16#include <asm/hwthread.h>
  17
  18#define PERF0VECINT             0x04820580
  19#define PERF1VECINT             0x04820588
  20#define PERF0TRIG_OFFSET        16
  21#define PERF1TRIG_OFFSET        17
  22
  23/**
  24 * struct metag_internal_irq_priv - private meta internal interrupt data
  25 * @domain:             IRQ domain for all internal Meta IRQs (HWSTATMETA)
  26 * @unmasked:           Record of unmasked IRQs
  27 */
  28struct metag_internal_irq_priv {
  29        struct irq_domain       *domain;
  30
  31        unsigned long           unmasked;
  32};
  33
  34/* Private data for the one and only internal interrupt controller */
  35static struct metag_internal_irq_priv metag_internal_irq_priv;
  36
  37static unsigned int metag_internal_irq_startup(struct irq_data *data);
  38static void metag_internal_irq_shutdown(struct irq_data *data);
  39static void metag_internal_irq_ack(struct irq_data *data);
  40static void metag_internal_irq_mask(struct irq_data *data);
  41static void metag_internal_irq_unmask(struct irq_data *data);
  42#ifdef CONFIG_SMP
  43static int metag_internal_irq_set_affinity(struct irq_data *data,
  44                        const struct cpumask *cpumask, bool force);
  45#endif
  46
  47static struct irq_chip internal_irq_edge_chip = {
  48        .name = "HWSTATMETA-IRQ",
  49        .irq_startup = metag_internal_irq_startup,
  50        .irq_shutdown = metag_internal_irq_shutdown,
  51        .irq_ack = metag_internal_irq_ack,
  52        .irq_mask = metag_internal_irq_mask,
  53        .irq_unmask = metag_internal_irq_unmask,
  54#ifdef CONFIG_SMP
  55        .irq_set_affinity = metag_internal_irq_set_affinity,
  56#endif
  57};
  58
  59/*
  60 *      metag_hwvec_addr - get the address of *VECINT regs of irq
  61 *
  62 *      This function is a table of supported triggers on HWSTATMETA
  63 *      Could do with a structure, but better keep it simple. Changes
  64 *      in this code should be rare.
  65 */
  66static inline void __iomem *metag_hwvec_addr(irq_hw_number_t hw)
  67{
  68        void __iomem *addr;
  69
  70        switch (hw) {
  71        case PERF0TRIG_OFFSET:
  72                addr = (void __iomem *)PERF0VECINT;
  73                break;
  74        case PERF1TRIG_OFFSET:
  75                addr = (void __iomem *)PERF1VECINT;
  76                break;
  77        default:
  78                addr = NULL;
  79                break;
  80        }
  81        return addr;
  82}
  83
  84/*
  85 *      metag_internal_startup - setup an internal irq
  86 *      @irq:   the irq to startup
  87 *
  88 *      Multiplex interrupts for @irq onto TR1. Clear any pending
  89 *      interrupts.
  90 */
  91static unsigned int metag_internal_irq_startup(struct irq_data *data)
  92{
  93        /* Clear (toggle) the bit in HWSTATMETA for our interrupt. */
  94        metag_internal_irq_ack(data);
  95
  96        /* Enable the interrupt by unmasking it */
  97        metag_internal_irq_unmask(data);
  98
  99        return 0;
 100}
 101
 102/*
 103 *      metag_internal_irq_shutdown - turn off the irq
 104 *      @irq:   the irq number to turn off
 105 *
 106 *      Mask @irq and clear any pending interrupts.
 107 *      Stop muxing @irq onto TR1.
 108 */
 109static void metag_internal_irq_shutdown(struct irq_data *data)
 110{
 111        /* Disable the IRQ at the core by masking it. */
 112        metag_internal_irq_mask(data);
 113
 114        /* Clear (toggle) the bit in HWSTATMETA for our interrupt. */
 115        metag_internal_irq_ack(data);
 116}
 117
 118/*
 119 *      metag_internal_irq_ack - acknowledge irq
 120 *      @irq:   the irq to ack
 121 */
 122static void metag_internal_irq_ack(struct irq_data *data)
 123{
 124        irq_hw_number_t hw = data->hwirq;
 125        unsigned int bit = 1 << hw;
 126
 127        if (metag_in32(HWSTATMETA) & bit)
 128                metag_out32(bit, HWSTATMETA);
 129}
 130
 131/**
 132 * metag_internal_irq_mask() - mask an internal irq by unvectoring
 133 * @data:       data for the internal irq to mask
 134 *
 135 * HWSTATMETA has no mask register. Instead the IRQ is unvectored from the core
 136 * and retriggered if necessary later.
 137 */
 138static void metag_internal_irq_mask(struct irq_data *data)
 139{
 140        struct metag_internal_irq_priv *priv = &metag_internal_irq_priv;
 141        irq_hw_number_t hw = data->hwirq;
 142        void __iomem *vec_addr = metag_hwvec_addr(hw);
 143
 144        clear_bit(hw, &priv->unmasked);
 145
 146        /* there is no interrupt mask, so unvector the interrupt */
 147        metag_out32(0, vec_addr);
 148}
 149
 150/**
 151 * meta_intc_unmask_edge_irq_nomask() - unmask an edge irq by revectoring
 152 * @data:       data for the internal irq to unmask
 153 *
 154 * HWSTATMETA has no mask register. Instead the IRQ is revectored back to the
 155 * core and retriggered if necessary.
 156 */
 157static void metag_internal_irq_unmask(struct irq_data *data)
 158{
 159        struct metag_internal_irq_priv *priv = &metag_internal_irq_priv;
 160        irq_hw_number_t hw = data->hwirq;
 161        unsigned int bit = 1 << hw;
 162        void __iomem *vec_addr = metag_hwvec_addr(hw);
 163        unsigned int thread = hard_processor_id();
 164
 165        set_bit(hw, &priv->unmasked);
 166
 167        /* there is no interrupt mask, so revector the interrupt */
 168        metag_out32(TBI_TRIG_VEC(TBID_SIGNUM_TR1(thread)), vec_addr);
 169
 170        /*
 171         * Re-trigger interrupt
 172         *
 173         * Writing a 1 toggles, and a 0->1 transition triggers. We only
 174         * retrigger if the status bit is already set, which means we
 175         * need to clear it first. Retriggering is fundamentally racy
 176         * because if the interrupt fires again after we clear it we
 177         * could end up clearing it again and the interrupt handler
 178         * thinking it hasn't fired. Therefore we need to keep trying to
 179         * retrigger until the bit is set.
 180         */
 181        if (metag_in32(HWSTATMETA) & bit) {
 182                metag_out32(bit, HWSTATMETA);
 183                while (!(metag_in32(HWSTATMETA) & bit))
 184                        metag_out32(bit, HWSTATMETA);
 185        }
 186}
 187
 188#ifdef CONFIG_SMP
 189/*
 190 *      metag_internal_irq_set_affinity - set the affinity for an interrupt
 191 */
 192static int metag_internal_irq_set_affinity(struct irq_data *data,
 193                        const struct cpumask *cpumask, bool force)
 194{
 195        unsigned int cpu, thread;
 196        irq_hw_number_t hw = data->hwirq;
 197        /*
 198         * Wire up this interrupt from *VECINT to the Meta core.
 199         *
 200         * Note that we can't wire up *VECINT to interrupt more than
 201         * one cpu (the interrupt code doesn't support it), so we just
 202         * pick the first cpu we find in 'cpumask'.
 203         */
 204        cpu = cpumask_any(cpumask);
 205        thread = cpu_2_hwthread_id[cpu];
 206
 207        metag_out32(TBI_TRIG_VEC(TBID_SIGNUM_TR1(thread)),
 208                    metag_hwvec_addr(hw));
 209
 210        return 0;
 211}
 212#endif
 213
 214/*
 215 *      metag_internal_irq_demux - irq de-multiplexer
 216 *      @irq:   the interrupt number
 217 *      @desc:  the interrupt description structure for this irq
 218 *
 219 *      The cpu receives an interrupt on TR1 when an interrupt has
 220 *      occurred. It is this function's job to demux this irq and
 221 *      figure out exactly which trigger needs servicing.
 222 */
 223static void metag_internal_irq_demux(unsigned int irq, struct irq_desc *desc)
 224{
 225        struct metag_internal_irq_priv *priv = irq_desc_get_handler_data(desc);
 226        irq_hw_number_t hw;
 227        unsigned int irq_no;
 228        u32 status;
 229
 230recalculate:
 231        status = metag_in32(HWSTATMETA) & priv->unmasked;
 232
 233        for (hw = 0; status != 0; status >>= 1, ++hw) {
 234                if (status & 0x1) {
 235                        /*
 236                         * Map the hardware IRQ number to a virtual Linux IRQ
 237                         * number.
 238                         */
 239                        irq_no = irq_linear_revmap(priv->domain, hw);
 240
 241                        /*
 242                         * Only fire off interrupts that are
 243                         * registered to be handled by the kernel.
 244                         * Other interrupts are probably being
 245                         * handled by other Meta hardware threads.
 246                         */
 247                        generic_handle_irq(irq_no);
 248
 249                        /*
 250                         * The handler may have re-enabled interrupts
 251                         * which could have caused a nested invocation
 252                         * of this code and make the copy of the
 253                         * status register we are using invalid.
 254                         */
 255                        goto recalculate;
 256                }
 257        }
 258}
 259
 260/**
 261 * internal_irq_map() - Map an internal meta IRQ to a virtual IRQ number.
 262 * @hw:         Number of the internal IRQ. Must be in range.
 263 *
 264 * Returns:     The virtual IRQ number of the Meta internal IRQ specified by
 265 *              @hw.
 266 */
 267int internal_irq_map(unsigned int hw)
 268{
 269        struct metag_internal_irq_priv *priv = &metag_internal_irq_priv;
 270        if (!priv->domain)
 271                return -ENODEV;
 272        return irq_create_mapping(priv->domain, hw);
 273}
 274
 275/**
 276 *      metag_internal_irq_init_cpu - regsister with the Meta cpu
 277 *      @cpu:   the CPU to register on
 278 *
 279 *      Configure @cpu's TR1 irq so that we can demux irqs.
 280 */
 281static void metag_internal_irq_init_cpu(struct metag_internal_irq_priv *priv,
 282                                        int cpu)
 283{
 284        unsigned int thread = cpu_2_hwthread_id[cpu];
 285        unsigned int signum = TBID_SIGNUM_TR1(thread);
 286        int irq = tbisig_map(signum);
 287
 288        /* Register the multiplexed IRQ handler */
 289        irq_set_handler_data(irq, priv);
 290        irq_set_chained_handler(irq, metag_internal_irq_demux);
 291        irq_set_irq_type(irq, IRQ_TYPE_LEVEL_LOW);
 292}
 293
 294/**
 295 * metag_internal_intc_map() - map an internal irq
 296 * @d:          irq domain of internal trigger block
 297 * @irq:        virtual irq number
 298 * @hw:         hardware irq number within internal trigger block
 299 *
 300 * This sets up a virtual irq for a specified hardware interrupt. The irq chip
 301 * and handler is configured.
 302 */
 303static int metag_internal_intc_map(struct irq_domain *d, unsigned int irq,
 304                                   irq_hw_number_t hw)
 305{
 306        /* only register interrupt if it is mapped */
 307        if (!metag_hwvec_addr(hw))
 308                return -EINVAL;
 309
 310        irq_set_chip_and_handler(irq, &internal_irq_edge_chip,
 311                                 handle_edge_irq);
 312        return 0;
 313}
 314
 315static const struct irq_domain_ops metag_internal_intc_domain_ops = {
 316        .map    = metag_internal_intc_map,
 317};
 318
 319/**
 320 *      metag_internal_irq_register - register internal IRQs
 321 *
 322 *      Register the irq chip and handler function for all internal IRQs
 323 */
 324int __init init_internal_IRQ(void)
 325{
 326        struct metag_internal_irq_priv *priv = &metag_internal_irq_priv;
 327        unsigned int cpu;
 328
 329        /* Set up an IRQ domain */
 330        priv->domain = irq_domain_add_linear(NULL, 32,
 331                                             &metag_internal_intc_domain_ops,
 332                                             priv);
 333        if (unlikely(!priv->domain)) {
 334                pr_err("meta-internal-intc: cannot add IRQ domain\n");
 335                return -ENOMEM;
 336        }
 337
 338        /* Setup TR1 for all cpus. */
 339        for_each_possible_cpu(cpu)
 340                metag_internal_irq_init_cpu(priv, cpu);
 341
 342        return 0;
 343};
 344
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.