linux/arch/arc/kernel/mcip.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * ARC ARConnect (MultiCore IP) support (formerly known as MCIP)
   4 *
   5 * Copyright (C) 2013 Synopsys, Inc. (www.synopsys.com)
   6 */
   7
   8#include <linux/smp.h>
   9#include <linux/irq.h>
  10#include <linux/irqchip/chained_irq.h>
  11#include <linux/spinlock.h>
  12#include <soc/arc/mcip.h>
  13#include <asm/irqflags-arcv2.h>
  14#include <asm/setup.h>
  15
  16static DEFINE_RAW_SPINLOCK(mcip_lock);
  17
  18#ifdef CONFIG_SMP
  19
  20static char smp_cpuinfo_buf[128];
  21
  22/*
  23 * Set mask to halt GFRC if any online core in SMP cluster is halted.
  24 * Only works for ARC HS v3.0+, on earlier versions has no effect.
  25 */
  26static void mcip_update_gfrc_halt_mask(int cpu)
  27{
  28        struct bcr_generic gfrc;
  29        unsigned long flags;
  30        u32 gfrc_halt_mask;
  31
  32        READ_BCR(ARC_REG_GFRC_BUILD, gfrc);
  33
  34        /*
  35         * CMD_GFRC_SET_CORE and CMD_GFRC_READ_CORE commands were added in
  36         * GFRC 0x3 version.
  37         */
  38        if (gfrc.ver < 0x3)
  39                return;
  40
  41        raw_spin_lock_irqsave(&mcip_lock, flags);
  42
  43        __mcip_cmd(CMD_GFRC_READ_CORE, 0);
  44        gfrc_halt_mask = read_aux_reg(ARC_REG_MCIP_READBACK);
  45        gfrc_halt_mask |= BIT(cpu);
  46        __mcip_cmd_data(CMD_GFRC_SET_CORE, 0, gfrc_halt_mask);
  47
  48        raw_spin_unlock_irqrestore(&mcip_lock, flags);
  49}
  50
  51static void mcip_update_debug_halt_mask(int cpu)
  52{
  53        u32 mcip_mask = 0;
  54        unsigned long flags;
  55
  56        raw_spin_lock_irqsave(&mcip_lock, flags);
  57
  58        /*
  59         * mcip_mask is same for CMD_DEBUG_SET_SELECT and CMD_DEBUG_SET_MASK
  60         * commands. So read it once instead of reading both CMD_DEBUG_READ_MASK
  61         * and CMD_DEBUG_READ_SELECT.
  62         */
  63        __mcip_cmd(CMD_DEBUG_READ_SELECT, 0);
  64        mcip_mask = read_aux_reg(ARC_REG_MCIP_READBACK);
  65
  66        mcip_mask |= BIT(cpu);
  67
  68        __mcip_cmd_data(CMD_DEBUG_SET_SELECT, 0, mcip_mask);
  69        /*
  70         * Parameter specified halt cause:
  71         * STATUS32[H]/actionpoint/breakpoint/self-halt
  72         * We choose all of them (0xF).
  73         */
  74        __mcip_cmd_data(CMD_DEBUG_SET_MASK, 0xF, mcip_mask);
  75
  76        raw_spin_unlock_irqrestore(&mcip_lock, flags);
  77}
  78
  79static void mcip_setup_per_cpu(int cpu)
  80{
  81        struct mcip_bcr mp;
  82
  83        READ_BCR(ARC_REG_MCIP_BCR, mp);
  84
  85        smp_ipi_irq_setup(cpu, IPI_IRQ);
  86        smp_ipi_irq_setup(cpu, SOFTIRQ_IRQ);
  87
  88        /* Update GFRC halt mask as new CPU came online */
  89        if (mp.gfrc)
  90                mcip_update_gfrc_halt_mask(cpu);
  91
  92        /* Update MCIP debug mask as new CPU came online */
  93        if (mp.dbg)
  94                mcip_update_debug_halt_mask(cpu);
  95}
  96
  97static void mcip_ipi_send(int cpu)
  98{
  99        unsigned long flags;
 100        int ipi_was_pending;
 101
 102        /* ARConnect can only send IPI to others */
 103        if (unlikely(cpu == raw_smp_processor_id())) {
 104                arc_softirq_trigger(SOFTIRQ_IRQ);
 105                return;
 106        }
 107
 108        raw_spin_lock_irqsave(&mcip_lock, flags);
 109
 110        /*
 111         * If receiver already has a pending interrupt, elide sending this one.
 112         * Linux cross core calling works well with concurrent IPIs
 113         * coalesced into one
 114         * see arch/arc/kernel/smp.c: ipi_send_msg_one()
 115         */
 116        __mcip_cmd(CMD_INTRPT_READ_STATUS, cpu);
 117        ipi_was_pending = read_aux_reg(ARC_REG_MCIP_READBACK);
 118        if (!ipi_was_pending)
 119                __mcip_cmd(CMD_INTRPT_GENERATE_IRQ, cpu);
 120
 121        raw_spin_unlock_irqrestore(&mcip_lock, flags);
 122}
 123
 124static void mcip_ipi_clear(int irq)
 125{
 126        unsigned int cpu, c;
 127        unsigned long flags;
 128
 129        if (unlikely(irq == SOFTIRQ_IRQ)) {
 130                arc_softirq_clear(irq);
 131                return;
 132        }
 133
 134        raw_spin_lock_irqsave(&mcip_lock, flags);
 135
 136        /* Who sent the IPI */
 137        __mcip_cmd(CMD_INTRPT_CHECK_SOURCE, 0);
 138
 139        cpu = read_aux_reg(ARC_REG_MCIP_READBACK);      /* 1,2,4,8... */
 140
 141        /*
 142         * In rare case, multiple concurrent IPIs sent to same target can
 143         * possibly be coalesced by MCIP into 1 asserted IRQ, so @cpus can be
 144         * "vectored" (multiple bits sets) as opposed to typical single bit
 145         */
 146        do {
 147                c = __ffs(cpu);                 /* 0,1,2,3 */
 148                __mcip_cmd(CMD_INTRPT_GENERATE_ACK, c);
 149                cpu &= ~(1U << c);
 150        } while (cpu);
 151
 152        raw_spin_unlock_irqrestore(&mcip_lock, flags);
 153}
 154
 155static void mcip_probe_n_setup(void)
 156{
 157        struct mcip_bcr mp;
 158
 159        READ_BCR(ARC_REG_MCIP_BCR, mp);
 160
 161        sprintf(smp_cpuinfo_buf,
 162                "Extn [SMP]\t: ARConnect (v%d): %d cores with %s%s%s%s\n",
 163                mp.ver, mp.num_cores,
 164                IS_AVAIL1(mp.ipi, "IPI "),
 165                IS_AVAIL1(mp.idu, "IDU "),
 166                IS_AVAIL1(mp.dbg, "DEBUG "),
 167                IS_AVAIL1(mp.gfrc, "GFRC"));
 168
 169        cpuinfo_arc700[0].extn.gfrc = mp.gfrc;
 170}
 171
 172struct plat_smp_ops plat_smp_ops = {
 173        .info           = smp_cpuinfo_buf,
 174        .init_early_smp = mcip_probe_n_setup,
 175        .init_per_cpu   = mcip_setup_per_cpu,
 176        .ipi_send       = mcip_ipi_send,
 177        .ipi_clear      = mcip_ipi_clear,
 178};
 179
 180#endif
 181
 182/***************************************************************************
 183 * ARCv2 Interrupt Distribution Unit (IDU)
 184 *
 185 * Connects external "COMMON" IRQs to core intc, providing:
 186 *  -dynamic routing (IRQ affinity)
 187 *  -load balancing (Round Robin interrupt distribution)
 188 *  -1:N distribution
 189 *
 190 * It physically resides in the MCIP hw block
 191 */
 192
 193#include <linux/irqchip.h>
 194#include <linux/of.h>
 195#include <linux/of_irq.h>
 196
 197/*
 198 * Set the DEST for @cmn_irq to @cpu_mask (1 bit per core)
 199 */
 200static void idu_set_dest(unsigned int cmn_irq, unsigned int cpu_mask)
 201{
 202        __mcip_cmd_data(CMD_IDU_SET_DEST, cmn_irq, cpu_mask);
 203}
 204
 205static void idu_set_mode(unsigned int cmn_irq, bool set_lvl, unsigned int lvl,
 206                         bool set_distr, unsigned int distr)
 207{
 208        union {
 209                unsigned int word;
 210                struct {
 211                        unsigned int distr:2, pad:2, lvl:1, pad2:27;
 212                };
 213        } data;
 214
 215        data.word = __mcip_cmd_read(CMD_IDU_READ_MODE, cmn_irq);
 216        if (set_distr)
 217                data.distr = distr;
 218        if (set_lvl)
 219                data.lvl = lvl;
 220        __mcip_cmd_data(CMD_IDU_SET_MODE, cmn_irq, data.word);
 221}
 222
 223static void idu_irq_mask_raw(irq_hw_number_t hwirq)
 224{
 225        unsigned long flags;
 226
 227        raw_spin_lock_irqsave(&mcip_lock, flags);
 228        __mcip_cmd_data(CMD_IDU_SET_MASK, hwirq, 1);
 229        raw_spin_unlock_irqrestore(&mcip_lock, flags);
 230}
 231
 232static void idu_irq_mask(struct irq_data *data)
 233{
 234        idu_irq_mask_raw(data->hwirq);
 235}
 236
 237static void idu_irq_unmask(struct irq_data *data)
 238{
 239        unsigned long flags;
 240
 241        raw_spin_lock_irqsave(&mcip_lock, flags);
 242        __mcip_cmd_data(CMD_IDU_SET_MASK, data->hwirq, 0);
 243        raw_spin_unlock_irqrestore(&mcip_lock, flags);
 244}
 245
 246static void idu_irq_ack(struct irq_data *data)
 247{
 248        unsigned long flags;
 249
 250        raw_spin_lock_irqsave(&mcip_lock, flags);
 251        __mcip_cmd(CMD_IDU_ACK_CIRQ, data->hwirq);
 252        raw_spin_unlock_irqrestore(&mcip_lock, flags);
 253}
 254
 255static void idu_irq_mask_ack(struct irq_data *data)
 256{
 257        unsigned long flags;
 258
 259        raw_spin_lock_irqsave(&mcip_lock, flags);
 260        __mcip_cmd_data(CMD_IDU_SET_MASK, data->hwirq, 1);
 261        __mcip_cmd(CMD_IDU_ACK_CIRQ, data->hwirq);
 262        raw_spin_unlock_irqrestore(&mcip_lock, flags);
 263}
 264
 265static int
 266idu_irq_set_affinity(struct irq_data *data, const struct cpumask *cpumask,
 267                     bool force)
 268{
 269        unsigned long flags;
 270        cpumask_t online;
 271        unsigned int destination_bits;
 272        unsigned int distribution_mode;
 273
 274        /* errout if no online cpu per @cpumask */
 275        if (!cpumask_and(&online, cpumask, cpu_online_mask))
 276                return -EINVAL;
 277
 278        raw_spin_lock_irqsave(&mcip_lock, flags);
 279
 280        destination_bits = cpumask_bits(&online)[0];
 281        idu_set_dest(data->hwirq, destination_bits);
 282
 283        if (ffs(destination_bits) == fls(destination_bits))
 284                distribution_mode = IDU_M_DISTRI_DEST;
 285        else
 286                distribution_mode = IDU_M_DISTRI_RR;
 287
 288        idu_set_mode(data->hwirq, false, 0, true, distribution_mode);
 289
 290        raw_spin_unlock_irqrestore(&mcip_lock, flags);
 291
 292        return IRQ_SET_MASK_OK;
 293}
 294
 295static int idu_irq_set_type(struct irq_data *data, u32 type)
 296{
 297        unsigned long flags;
 298
 299        /*
 300         * ARCv2 IDU HW does not support inverse polarity, so these are the
 301         * only interrupt types supported.
 302         */
 303        if (type & ~(IRQ_TYPE_EDGE_RISING | IRQ_TYPE_LEVEL_HIGH))
 304                return -EINVAL;
 305
 306        raw_spin_lock_irqsave(&mcip_lock, flags);
 307
 308        idu_set_mode(data->hwirq, true,
 309                     type & IRQ_TYPE_EDGE_RISING ? IDU_M_TRIG_EDGE :
 310                                                   IDU_M_TRIG_LEVEL,
 311                     false, 0);
 312
 313        raw_spin_unlock_irqrestore(&mcip_lock, flags);
 314
 315        return 0;
 316}
 317
 318static void idu_irq_enable(struct irq_data *data)
 319{
 320        /*
 321         * By default send all common interrupts to all available online CPUs.
 322         * The affinity of common interrupts in IDU must be set manually since
 323         * in some cases the kernel will not call irq_set_affinity() by itself:
 324         *   1. When the kernel is not configured with support of SMP.
 325         *   2. When the kernel is configured with support of SMP but upper
 326         *      interrupt controllers does not support setting of the affinity
 327         *      and cannot propagate it to IDU.
 328         */
 329        idu_irq_set_affinity(data, cpu_online_mask, false);
 330        idu_irq_unmask(data);
 331}
 332
 333static struct irq_chip idu_irq_chip = {
 334        .name                   = "MCIP IDU Intc",
 335        .irq_mask               = idu_irq_mask,
 336        .irq_unmask             = idu_irq_unmask,
 337        .irq_ack                = idu_irq_ack,
 338        .irq_mask_ack           = idu_irq_mask_ack,
 339        .irq_enable             = idu_irq_enable,
 340        .irq_set_type           = idu_irq_set_type,
 341#ifdef CONFIG_SMP
 342        .irq_set_affinity       = idu_irq_set_affinity,
 343#endif
 344
 345};
 346
 347static void idu_cascade_isr(struct irq_desc *desc)
 348{
 349        struct irq_domain *idu_domain = irq_desc_get_handler_data(desc);
 350        struct irq_chip *core_chip = irq_desc_get_chip(desc);
 351        irq_hw_number_t core_hwirq = irqd_to_hwirq(irq_desc_get_irq_data(desc));
 352        irq_hw_number_t idu_hwirq = core_hwirq - FIRST_EXT_IRQ;
 353
 354        chained_irq_enter(core_chip, desc);
 355        generic_handle_domain_irq(idu_domain, idu_hwirq);
 356        chained_irq_exit(core_chip, desc);
 357}
 358
 359static int idu_irq_map(struct irq_domain *d, unsigned int virq, irq_hw_number_t hwirq)
 360{
 361        irq_set_chip_and_handler(virq, &idu_irq_chip, handle_level_irq);
 362        irq_set_status_flags(virq, IRQ_MOVE_PCNTXT);
 363
 364        return 0;
 365}
 366
 367static const struct irq_domain_ops idu_irq_ops = {
 368        .xlate  = irq_domain_xlate_onetwocell,
 369        .map    = idu_irq_map,
 370};
 371
 372/*
 373 * [16, 23]: Statically assigned always private-per-core (Timers, WDT, IPI)
 374 * [24, 23+C]: If C > 0 then "C" common IRQs
 375 * [24+C, N]: Not statically assigned, private-per-core
 376 */
 377
 378
 379static int __init
 380idu_of_init(struct device_node *intc, struct device_node *parent)
 381{
 382        struct irq_domain *domain;
 383        int nr_irqs;
 384        int i, virq;
 385        struct mcip_bcr mp;
 386        struct mcip_idu_bcr idu_bcr;
 387
 388        READ_BCR(ARC_REG_MCIP_BCR, mp);
 389
 390        if (!mp.idu)
 391                panic("IDU not detected, but DeviceTree using it");
 392
 393        READ_BCR(ARC_REG_MCIP_IDU_BCR, idu_bcr);
 394        nr_irqs = mcip_idu_bcr_to_nr_irqs(idu_bcr);
 395
 396        pr_info("MCIP: IDU supports %u common irqs\n", nr_irqs);
 397
 398        domain = irq_domain_add_linear(intc, nr_irqs, &idu_irq_ops, NULL);
 399
 400        /* Parent interrupts (core-intc) are already mapped */
 401
 402        for (i = 0; i < nr_irqs; i++) {
 403                /* Mask all common interrupts by default */
 404                idu_irq_mask_raw(i);
 405
 406                /*
 407                 * Return parent uplink IRQs (towards core intc) 24,25,.....
 408                 * this step has been done before already
 409                 * however we need it to get the parent virq and set IDU handler
 410                 * as first level isr
 411                 */
 412                virq = irq_create_mapping(NULL, i + FIRST_EXT_IRQ);
 413                BUG_ON(!virq);
 414                irq_set_chained_handler_and_data(virq, idu_cascade_isr, domain);
 415        }
 416
 417        __mcip_cmd(CMD_IDU_ENABLE, 0);
 418
 419        return 0;
 420}
 421IRQCHIP_DECLARE(arcv2_idu_intc, "snps,archs-idu-intc", idu_of_init);
 422