smp.c 4.4 KB
Newer Older
V
Vineet Gupta 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
/*
 * ARC700 Simulation-only Extensions for SMP
 *
 * Copyright (C) 2004, 2007-2010, 2011-2012 Synopsys, Inc. (www.synopsys.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 *  Vineet Gupta    - 2012 : split off arch common and plat specific SMP
 *  Rajeshwar Ranga - 2007 : Interrupt Distribution Unit API's
 */

#include <linux/smp.h>
#include <asm/irq.h>
#include <plat/smp.h>

static char smp_cpuinfo_buf[128];

/*
 *-------------------------------------------------------------------
 * Platform specific callbacks expected by arch SMP code
 *-------------------------------------------------------------------
 */

const char *arc_platform_smp_cpuinfo(void)
{
#define IS_AVAIL1(var, str)    ((var) ? str : "")

	struct bcr_mp mp;

	READ_BCR(ARC_REG_MP_BCR, mp);

	sprintf(smp_cpuinfo_buf, "Extn [700-SMP]: v%d, arch(%d) %s %s %s\n",
		mp.ver, mp.mp_arch, IS_AVAIL1(mp.scu, "SCU"),
		IS_AVAIL1(mp.idu, "IDU"), IS_AVAIL1(mp.sdu, "SDU"));

	return smp_cpuinfo_buf;
}

/*
 * Master kick starting another CPU
 */
void arc_platform_smp_wakeup_cpu(int cpu, unsigned long pc)
{
	/* setup the start PC */
	write_aux_reg(ARC_AUX_XTL_REG_PARAM, pc);

	/* Trigger WRITE_PC cmd for this cpu */
	write_aux_reg(ARC_AUX_XTL_REG_CMD,
			(ARC_XTL_CMD_WRITE_PC | (cpu << 8)));

	/* Take the cpu out of Halt */
	write_aux_reg(ARC_AUX_XTL_REG_CMD,
			(ARC_XTL_CMD_CLEAR_HALT | (cpu << 8)));

}

/*
 * Any SMP specific init any CPU does when it comes up.
 * Here we setup the CPU to enable Inter-Processor-Interrupts
 * Called for each CPU
 * -Master      : init_IRQ()
 * -Other(s)    : start_kernel_secondary()
 */
void arc_platform_smp_init_cpu(void)
{
	int cpu = smp_processor_id();

	/* Check if CPU is configured for more than 16 interrupts */
	if (NR_IRQS <= 16 || get_hw_config_num_irq() <= 16)
		panic("[arcfpga] IRQ system can't support IDU IPI\n");

	idu_disable();

	/****************************************************************
	 * IDU provides a set of Common IRQs, each of which can be dynamically
	 * attached to (1|many|all) CPUs.
	 * The Common IRQs [0-15] are mapped as CPU pvt [16-31]
	 *
	 * Here we use a simple 1:1 mapping:
	 * A CPU 'x' is wired to Common IRQ 'x'.
	 * So an IDU ASSERT on IRQ 'x' will trigger Interupt on CPU 'x', which
	 * makes up for our simple IPI plumbing.
	 *
	 * TBD: Have a dedicated multicast IRQ for sending IPIs to all CPUs
	 *      w/o having to do one-at-a-time
	 ******************************************************************/

	/*
	 * Claim an IRQ which would trigger IPI on this CPU.
	 * In IDU parlance it involves setting up a cpu bitmask for the IRQ
	 * The bitmap here contains only 1 CPU (self).
	 */
	idu_irq_set_tgtcpu(cpu, 0x1 << cpu);

	/* Set the IRQ destination to use the bitmask above */
	idu_irq_set_mode(cpu, 7, /* XXX: IDU_IRQ_MOD_TCPU_ALLRECP: ISS bug */
			 IDU_IRQ_MODE_PULSE_TRIG);

	idu_enable();

	/* Attach the arch-common IPI ISR to our IDU IRQ */
	smp_ipi_irq_setup(cpu, IDU_INTERRUPT_0 + cpu);
}

void arc_platform_ipi_send(const struct cpumask *callmap)
{
	unsigned int cpu;

	for_each_cpu(cpu, callmap)
		idu_irq_assert(cpu);
}

void arc_platform_ipi_clear(int cpu, int irq)
{
	idu_irq_clear(IDU_INTERRUPT_0 + cpu);
}

/*
 *-------------------------------------------------------------------
 * Low level Platform IPI Providers
 *-------------------------------------------------------------------
 */

/* Set the Mode for the Common IRQ */
void idu_irq_set_mode(uint8_t irq, uint8_t dest_mode, uint8_t trig_mode)
{
	uint32_t par = IDU_IRQ_MODE_PARAM(dest_mode, trig_mode);

	IDU_SET_PARAM(par);
	IDU_SET_COMMAND(irq, IDU_IRQ_WMODE);
}

/* Set the target cpu Bitmask for Common IRQ */
void idu_irq_set_tgtcpu(uint8_t irq, uint32_t mask)
{
	IDU_SET_PARAM(mask);
	IDU_SET_COMMAND(irq, IDU_IRQ_WBITMASK);
}

/* Get the Interrupt Acknowledged status for IRQ (as CPU Bitmask) */
bool idu_irq_get_ack(uint8_t irq)
{
	uint32_t val;

	IDU_SET_COMMAND(irq, IDU_IRQ_ACK);
	val = IDU_GET_PARAM();

	return val & (1 << irq);
}

/*
 * Get the Interrupt Pending status for IRQ (as CPU Bitmask)
 * -Pending means CPU has not yet noticed the IRQ (e.g. disabled)
 * -After Interrupt has been taken, the IPI expcitily needs to be
 *  cleared, to be acknowledged.
 */
bool idu_irq_get_pend(uint8_t irq)
{
	uint32_t val;

	IDU_SET_COMMAND(irq, IDU_IRQ_PEND);
	val = IDU_GET_PARAM();

	return val & (1 << irq);
}