linux/drivers/irqchip/irq-armada-370-xp.c
<<
>>
Prefs
   1/*
   2 * Marvell Armada 370 and Armada XP SoC IRQ handling
   3 *
   4 * Copyright (C) 2012 Marvell
   5 *
   6 * Lior Amsalem <alior@marvell.com>
   7 * Gregory CLEMENT <gregory.clement@free-electrons.com>
   8 * Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
   9 * Ben Dooks <ben.dooks@codethink.co.uk>
  10 *
  11 * This file is licensed under the terms of the GNU General Public
  12 * License version 2.  This program is licensed "as is" without any
  13 * warranty of any kind, whether express or implied.
  14 */
  15
  16#include <linux/kernel.h>
  17#include <linux/module.h>
  18#include <linux/init.h>
  19#include <linux/irq.h>
  20#include <linux/interrupt.h>
  21#include <linux/io.h>
  22#include <linux/of_address.h>
  23#include <linux/of_irq.h>
  24#include <linux/irqdomain.h>
  25#include <asm/mach/arch.h>
  26#include <asm/exception.h>
  27#include <asm/smp_plat.h>
  28#include <asm/mach/irq.h>
  29
  30#include "irqchip.h"
  31
  32/* Interrupt Controller Registers Map */
  33#define ARMADA_370_XP_INT_SET_MASK_OFFS         (0x48)
  34#define ARMADA_370_XP_INT_CLEAR_MASK_OFFS       (0x4C)
  35
  36#define ARMADA_370_XP_INT_CONTROL               (0x00)
  37#define ARMADA_370_XP_INT_SET_ENABLE_OFFS       (0x30)
  38#define ARMADA_370_XP_INT_CLEAR_ENABLE_OFFS     (0x34)
  39#define ARMADA_370_XP_INT_SOURCE_CTL(irq)       (0x100 + irq*4)
  40
  41#define ARMADA_370_XP_CPU_INTACK_OFFS           (0x44)
  42
  43#define ARMADA_370_XP_SW_TRIG_INT_OFFS           (0x4)
  44#define ARMADA_370_XP_IN_DRBEL_MSK_OFFS          (0xc)
  45#define ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS        (0x8)
  46
  47#define ARMADA_370_XP_MAX_PER_CPU_IRQS          (28)
  48
  49#define ARMADA_370_XP_TIMER0_PER_CPU_IRQ        (5)
  50
  51#define IPI_DOORBELL_START                      (0)
  52#define IPI_DOORBELL_END                        (8)
  53#define IPI_DOORBELL_MASK                       0xFF
  54
  55static DEFINE_RAW_SPINLOCK(irq_controller_lock);
  56
  57static void __iomem *per_cpu_int_base;
  58static void __iomem *main_int_base;
  59static struct irq_domain *armada_370_xp_mpic_domain;
  60
  61/*
  62 * In SMP mode:
  63 * For shared global interrupts, mask/unmask global enable bit
  64 * For CPU interrupts, mask/unmask the calling CPU's bit
  65 */
  66static void armada_370_xp_irq_mask(struct irq_data *d)
  67{
  68        irq_hw_number_t hwirq = irqd_to_hwirq(d);
  69
  70        if (hwirq != ARMADA_370_XP_TIMER0_PER_CPU_IRQ)
  71                writel(hwirq, main_int_base +
  72                                ARMADA_370_XP_INT_CLEAR_ENABLE_OFFS);
  73        else
  74                writel(hwirq, per_cpu_int_base +
  75                                ARMADA_370_XP_INT_SET_MASK_OFFS);
  76}
  77
  78static void armada_370_xp_irq_unmask(struct irq_data *d)
  79{
  80        irq_hw_number_t hwirq = irqd_to_hwirq(d);
  81
  82        if (hwirq != ARMADA_370_XP_TIMER0_PER_CPU_IRQ)
  83                writel(hwirq, main_int_base +
  84                                ARMADA_370_XP_INT_SET_ENABLE_OFFS);
  85        else
  86                writel(hwirq, per_cpu_int_base +
  87                                ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
  88}
  89
  90#ifdef CONFIG_SMP
  91static int armada_xp_set_affinity(struct irq_data *d,
  92                                  const struct cpumask *mask_val, bool force)
  93{
  94        unsigned long reg;
  95        unsigned long new_mask = 0;
  96        unsigned long online_mask = 0;
  97        unsigned long count = 0;
  98        irq_hw_number_t hwirq = irqd_to_hwirq(d);
  99        int cpu;
 100
 101        for_each_cpu(cpu, mask_val) {
 102                new_mask |= 1 << cpu_logical_map(cpu);
 103                count++;
 104        }
 105
 106        /*
 107         * Forbid mutlicore interrupt affinity
 108         * This is required since the MPIC HW doesn't limit
 109         * several CPUs from acknowledging the same interrupt.
 110         */
 111        if (count > 1)
 112                return -EINVAL;
 113
 114        for_each_cpu(cpu, cpu_online_mask)
 115                online_mask |= 1 << cpu_logical_map(cpu);
 116
 117        raw_spin_lock(&irq_controller_lock);
 118
 119        reg = readl(main_int_base + ARMADA_370_XP_INT_SOURCE_CTL(hwirq));
 120        reg = (reg & (~online_mask)) | new_mask;
 121        writel(reg, main_int_base + ARMADA_370_XP_INT_SOURCE_CTL(hwirq));
 122
 123        raw_spin_unlock(&irq_controller_lock);
 124
 125        return 0;
 126}
 127#endif
 128
 129static struct irq_chip armada_370_xp_irq_chip = {
 130        .name           = "armada_370_xp_irq",
 131        .irq_mask       = armada_370_xp_irq_mask,
 132        .irq_mask_ack   = armada_370_xp_irq_mask,
 133        .irq_unmask     = armada_370_xp_irq_unmask,
 134#ifdef CONFIG_SMP
 135        .irq_set_affinity = armada_xp_set_affinity,
 136#endif
 137};
 138
 139static int armada_370_xp_mpic_irq_map(struct irq_domain *h,
 140                                      unsigned int virq, irq_hw_number_t hw)
 141{
 142        armada_370_xp_irq_mask(irq_get_irq_data(virq));
 143        if (hw != ARMADA_370_XP_TIMER0_PER_CPU_IRQ)
 144                writel(hw, per_cpu_int_base +
 145                        ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
 146        else
 147                writel(hw, main_int_base + ARMADA_370_XP_INT_SET_ENABLE_OFFS);
 148        irq_set_status_flags(virq, IRQ_LEVEL);
 149
 150        if (hw == ARMADA_370_XP_TIMER0_PER_CPU_IRQ) {
 151                irq_set_percpu_devid(virq);
 152                irq_set_chip_and_handler(virq, &armada_370_xp_irq_chip,
 153                                        handle_percpu_devid_irq);
 154
 155        } else {
 156                irq_set_chip_and_handler(virq, &armada_370_xp_irq_chip,
 157                                        handle_level_irq);
 158        }
 159        set_irq_flags(virq, IRQF_VALID | IRQF_PROBE);
 160
 161        return 0;
 162}
 163
 164#ifdef CONFIG_SMP
 165void armada_mpic_send_doorbell(const struct cpumask *mask, unsigned int irq)
 166{
 167        int cpu;
 168        unsigned long map = 0;
 169
 170        /* Convert our logical CPU mask into a physical one. */
 171        for_each_cpu(cpu, mask)
 172                map |= 1 << cpu_logical_map(cpu);
 173
 174        /*
 175         * Ensure that stores to Normal memory are visible to the
 176         * other CPUs before issuing the IPI.
 177         */
 178        dsb();
 179
 180        /* submit softirq */
 181        writel((map << 8) | irq, main_int_base +
 182                ARMADA_370_XP_SW_TRIG_INT_OFFS);
 183}
 184
 185void armada_xp_mpic_smp_cpu_init(void)
 186{
 187        /* Clear pending IPIs */
 188        writel(0, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS);
 189
 190        /* Enable first 8 IPIs */
 191        writel(IPI_DOORBELL_MASK, per_cpu_int_base +
 192                ARMADA_370_XP_IN_DRBEL_MSK_OFFS);
 193
 194        /* Unmask IPI interrupt */
 195        writel(0, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
 196}
 197#endif /* CONFIG_SMP */
 198
 199static struct irq_domain_ops armada_370_xp_mpic_irq_ops = {
 200        .map = armada_370_xp_mpic_irq_map,
 201        .xlate = irq_domain_xlate_onecell,
 202};
 203
 204static asmlinkage void __exception_irq_entry
 205armada_370_xp_handle_irq(struct pt_regs *regs)
 206{
 207        u32 irqstat, irqnr;
 208
 209        do {
 210                irqstat = readl_relaxed(per_cpu_int_base +
 211                                        ARMADA_370_XP_CPU_INTACK_OFFS);
 212                irqnr = irqstat & 0x3FF;
 213
 214                if (irqnr > 1022)
 215                        break;
 216
 217                if (irqnr > 0) {
 218                        irqnr = irq_find_mapping(armada_370_xp_mpic_domain,
 219                                        irqnr);
 220                        handle_IRQ(irqnr, regs);
 221                        continue;
 222                }
 223#ifdef CONFIG_SMP
 224                /* IPI Handling */
 225                if (irqnr == 0) {
 226                        u32 ipimask, ipinr;
 227
 228                        ipimask = readl_relaxed(per_cpu_int_base +
 229                                                ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS)
 230                                & IPI_DOORBELL_MASK;
 231
 232                        writel(~IPI_DOORBELL_MASK, per_cpu_int_base +
 233                                ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS);
 234
 235                        /* Handle all pending doorbells */
 236                        for (ipinr = IPI_DOORBELL_START;
 237                             ipinr < IPI_DOORBELL_END; ipinr++) {
 238                                if (ipimask & (0x1 << ipinr))
 239                                        handle_IPI(ipinr, regs);
 240                        }
 241                        continue;
 242                }
 243#endif
 244
 245        } while (1);
 246}
 247
 248static int __init armada_370_xp_mpic_of_init(struct device_node *node,
 249                                             struct device_node *parent)
 250{
 251        u32 control;
 252
 253        main_int_base = of_iomap(node, 0);
 254        per_cpu_int_base = of_iomap(node, 1);
 255
 256        BUG_ON(!main_int_base);
 257        BUG_ON(!per_cpu_int_base);
 258
 259        control = readl(main_int_base + ARMADA_370_XP_INT_CONTROL);
 260
 261        armada_370_xp_mpic_domain =
 262                irq_domain_add_linear(node, (control >> 2) & 0x3ff,
 263                                &armada_370_xp_mpic_irq_ops, NULL);
 264
 265        if (!armada_370_xp_mpic_domain)
 266                panic("Unable to add Armada_370_Xp MPIC irq domain (DT)\n");
 267
 268        irq_set_default_host(armada_370_xp_mpic_domain);
 269
 270#ifdef CONFIG_SMP
 271        armada_xp_mpic_smp_cpu_init();
 272
 273        /*
 274         * Set the default affinity from all CPUs to the boot cpu.
 275         * This is required since the MPIC doesn't limit several CPUs
 276         * from acknowledging the same interrupt.
 277         */
 278        cpumask_clear(irq_default_affinity);
 279        cpumask_set_cpu(smp_processor_id(), irq_default_affinity);
 280
 281#endif
 282
 283        set_handle_irq(armada_370_xp_handle_irq);
 284
 285        return 0;
 286}
 287
 288IRQCHIP_DECLARE(armada_370_xp_mpic, "marvell,mpic", armada_370_xp_mpic_of_init);
 289
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.