diff mbox

[08/10] arm: zynq: Add smp support

Message ID 1364219596-4954-8-git-send-email-michal.simek@xilinx.com (mailing list archive)
State New, archived
Headers show

Commit Message

Michal Simek March 25, 2013, 1:53 p.m. UTC
Zynq is dual core Cortex A9 which starts always
at zero. Using simple trampoline ensure long jump
to secondary_startup code.

Signed-off-by: Michal Simek <michal.simek@xilinx.com>
---
 arch/arm/mach-zynq/Makefile  |    1 +
 arch/arm/mach-zynq/common.c  |    1 +
 arch/arm/mach-zynq/common.h  |    7 ++
 arch/arm/mach-zynq/platsmp.c |  160 ++++++++++++++++++++++++++++++++++++++++++
 arch/arm/mach-zynq/slcr.c    |   29 ++++++++
 5 files changed, 198 insertions(+)
 create mode 100644 arch/arm/mach-zynq/platsmp.c

Comments

Rob Herring March 25, 2013, 2:16 p.m. UTC | #1
On 03/25/2013 08:53 AM, Michal Simek wrote:
> Zynq is dual core Cortex A9 which starts always
> at zero. Using simple trampoline ensure long jump
> to secondary_startup code.
> 
> Signed-off-by: Michal Simek <michal.simek@xilinx.com>
> ---
>  arch/arm/mach-zynq/Makefile  |    1 +
>  arch/arm/mach-zynq/common.c  |    1 +
>  arch/arm/mach-zynq/common.h  |    7 ++
>  arch/arm/mach-zynq/platsmp.c |  160 ++++++++++++++++++++++++++++++++++++++++++
>  arch/arm/mach-zynq/slcr.c    |   29 ++++++++
>  5 files changed, 198 insertions(+)
>  create mode 100644 arch/arm/mach-zynq/platsmp.c

[...]

> +/* Secondary CPU kernel startup is a 2 step process. The primary CPU
> + * starts the secondary CPU by giving it the address of the kernel and
> + * then sending it an event to wake it up. The secondary CPU then
> + * starts the kernel and tells the primary CPU it's up and running.
> + */
> +static void __cpuinit zynq_secondary_init(unsigned int cpu)
> +{
> +	/*
> +	 * if any interrupts are already enabled for the primary
> +	 * core (e.g. timer irq), then they will not have been enabled
> +	 * for us: do so
> +	 */
> +	gic_secondary_init(0);
> +
> +	/*
> +	 * Synchronise with the boot thread.
> +	 */
> +	spin_lock(&boot_lock);
> +	spin_unlock(&boot_lock);

Why do you think this is needed? Platforms that need this
synchronization are only the ones that just do wfi for hotplug rather
than properly reseting the core. You appear to do the latter and should
not need this.

> +}
> +
> +int __cpuinit zynq_cpun_start(u32 address, int cpu)
> +{
> +	if (cpu > ncores) {
> +		pr_warn("CPU No. is not available in the system\n");
> +		return -1;
> +	}
> +
> +	/* MS: Expectation that SLCR are directly map and accessible */
> +	/* Not possible to jump to non aligned address */
> +	if (!(address & 3) && (!address || (address >= 0xC))) {

What about Thumb2 kernel entry?

> +		slcr_cpu_stop(cpu);

Isn't a secondary cpu already stopped?

> +
> +		/*
> +		 * This is elegant way how to jump to any address
> +		 * 0x0: Load address at 0x8 to r0
> +		 * 0x4: Jump by mov instruction
> +		 * 0x8: Jumping address
> +		 */
> +		if (address) {
> +			/* 0: ldr r0, [8] */
> +			__raw_writel(0xe59f0000, phys_to_virt(0x0));
> +			/* 4: mov pc, r0 */
> +			__raw_writel(0xe1a0f000, phys_to_virt(0x4));
> +			__raw_writel(address, phys_to_virt(0x8));
> +		}
> +
> +		flush_cache_all();
> +		outer_flush_all();

You should only need to flush range on address 0-4 here.

> +		wmb();
> +
> +		slcr_cpu_start(cpu);
> +
> +		return 0;
> +	}
> +
> +	pr_warn("Can't start CPU%d: Wrong starting address %x\n", cpu, address);
> +
> +	return -1;
> +}
> +EXPORT_SYMBOL(zynq_cpun_start);
> +
> +static int __cpuinit zynq_boot_secondary(unsigned int cpu,
> +						struct task_struct *idle)
> +{
> +	int ret;
> +
> +	/*
> +	 * set synchronisation state between this boot processor
> +	 * and the secondary one
> +	 */
> +	spin_lock(&boot_lock);
> +
> +	ret = zynq_cpun_start(virt_to_phys(secondary_startup), cpu);
> +	if (ret) {
> +		spin_unlock(&boot_lock);
> +		return -1;
> +	}
> +
> +	/*
> +	 * now the secondary core is starting up let it run its
> +	 * calibrations, then wait for it to finish
> +	 */
> +	spin_unlock(&boot_lock);
> +
> +	return 0;
> +}
> +
> +/*
> + * Initialise the CPU possible map early - this describes the CPUs
> + * which may be present or become present in the system.
> + */
> +static void __init zynq_smp_init_cpus(void)
> +{
> +	int i;
> +
> +	ncores = scu_get_core_count(scu_base);
> +
> +	for (i = 0; i < ncores && i < CONFIG_NR_CPUS; i++)
> +		set_cpu_possible(i, true);
> +}
> +
> +static void __init zynq_smp_prepare_cpus(unsigned int max_cpus)
> +{
> +	int i;
> +
> +	/*
> +	 * Initialise the present map, which describes the set of CPUs
> +	 * actually populated at the present time.
> +	 */
> +	for (i = 0; i < max_cpus; i++)
> +		set_cpu_present(i, true);
> +
> +	scu_enable(scu_base);
> +}
> +
> +struct smp_operations zynq_smp_ops __initdata = {
> +	.smp_init_cpus		= zynq_smp_init_cpus,
> +	.smp_prepare_cpus	= zynq_smp_prepare_cpus,
> +	.smp_secondary_init	= zynq_secondary_init,
> +	.smp_boot_secondary	= zynq_boot_secondary,
> +};
> diff --git a/arch/arm/mach-zynq/slcr.c b/arch/arm/mach-zynq/slcr.c
> index 36b79d8..7f2a919 100644
> --- a/arch/arm/mach-zynq/slcr.c
> +++ b/arch/arm/mach-zynq/slcr.c
> @@ -33,6 +33,11 @@
>  #define SLCR_UNLOCK			0x8   /* SCLR unlock register */
>  
>  #define SLCR_PSS_RST_CTRL_OFFSET	0x200 /* PS Software Reset Control */
> +
> +#define SLCR_A9_CPU_CLKSTOP		0x10
> +#define SLCR_A9_CPU_RST			0x1
> +
> +#define SLCR_A9_CPU_RST_CTRL		0x244 /* CPU Software Reset Control */
>  #define SLCR_REBOOT_STATUS		0x258 /* PS Reboot Status */
>  
>  void __iomem *zynq_slcr_base;
> @@ -61,6 +66,30 @@ void slcr_system_reset(void)
>  }
>  
>  /**
> + * slcr_cpu_start - Start cpu
> + * @cpu:	cpu number
> + */
> +void slcr_cpu_start(int cpu)
> +{
> +	/* enable CPUn */
> +	writel(SLCR_A9_CPU_CLKSTOP << cpu,
> +	       zynq_slcr_base + SLCR_A9_CPU_RST_CTRL);
> +	/* enable CLK for CPUn */
> +	writel(0x0 << cpu, zynq_slcr_base + SLCR_A9_CPU_RST_CTRL);
> +}
> +
> +/**
> + * slcr_cpu_stop - Stop cpu
> + * @cpu:	cpu number
> + */
> +void slcr_cpu_stop(int cpu)
> +{
> +	/* stop CLK and reset CPUn */
> +	writel((SLCR_A9_CPU_CLKSTOP | SLCR_A9_CPU_RST) << cpu,
> +	       zynq_slcr_base + SLCR_A9_CPU_RST_CTRL);
> +}
> +
> +/**
>   * xslcr_init()
>   * Returns 0 on success, negative errno otherwise.
>   *
>
Michal Simek March 25, 2013, 4:31 p.m. UTC | #2
On 03/25/2013 03:16 PM, Rob Herring wrote:
> On 03/25/2013 08:53 AM, Michal Simek wrote:
>> Zynq is dual core Cortex A9 which starts always
>> at zero. Using simple trampoline ensure long jump
>> to secondary_startup code.
>>
>> Signed-off-by: Michal Simek <michal.simek@xilinx.com>
>> ---
>>   arch/arm/mach-zynq/Makefile  |    1 +
>>   arch/arm/mach-zynq/common.c  |    1 +
>>   arch/arm/mach-zynq/common.h  |    7 ++
>>   arch/arm/mach-zynq/platsmp.c |  160 ++++++++++++++++++++++++++++++++++++++++++
>>   arch/arm/mach-zynq/slcr.c    |   29 ++++++++
>>   5 files changed, 198 insertions(+)
>>   create mode 100644 arch/arm/mach-zynq/platsmp.c
>
> [...]
>
>> +/* Secondary CPU kernel startup is a 2 step process. The primary CPU
>> + * starts the secondary CPU by giving it the address of the kernel and
>> + * then sending it an event to wake it up. The secondary CPU then
>> + * starts the kernel and tells the primary CPU it's up and running.
>> + */
>> +static void __cpuinit zynq_secondary_init(unsigned int cpu)
>> +{
>> +	/*
>> +	 * if any interrupts are already enabled for the primary
>> +	 * core (e.g. timer irq), then they will not have been enabled
>> +	 * for us: do so
>> +	 */
>> +	gic_secondary_init(0);
>> +
>> +	/*
>> +	 * Synchronise with the boot thread.
>> +	 */
>> +	spin_lock(&boot_lock);
>> +	spin_unlock(&boot_lock);
>
> Why do you think this is needed? Platforms that need this
> synchronization are only the ones that just do wfi for hotplug rather
> than properly reseting the core. You appear to do the latter and should
> not need this.

ok.


>> +}
>> +
>> +int __cpuinit zynq_cpun_start(u32 address, int cpu)
>> +{
>> +	if (cpu > ncores) {
>> +		pr_warn("CPU No. is not available in the system\n");
>> +		return -1;
>> +	}
>> +
>> +	/* MS: Expectation that SLCR are directly map and accessible */
>> +	/* Not possible to jump to non aligned address */
>> +	if (!(address & 3) && (!address || (address >= 0xC))) {
>
> What about Thumb2 kernel entry?

I have no idea what's that.
Still more microblaze guy than Arm one.

>
>> +		slcr_cpu_stop(cpu);
>
> Isn't a secondary cpu already stopped?

On the normal boot this is really necessary because first stage bootloader
doesn't stop cpu just keep it in loop and without stopping cpu
and starting it again it doesn't work.

>> +
>> +		/*
>> +		 * This is elegant way how to jump to any address
>> +		 * 0x0: Load address at 0x8 to r0
>> +		 * 0x4: Jump by mov instruction
>> +		 * 0x8: Jumping address
>> +		 */
>> +		if (address) {
>> +			/* 0: ldr r0, [8] */
>> +			__raw_writel(0xe59f0000, phys_to_virt(0x0));
>> +			/* 4: mov pc, r0 */
>> +			__raw_writel(0xe1a0f000, phys_to_virt(0x4));
>> +			__raw_writel(address, phys_to_virt(0x8));
>> +		}
>> +
>> +		flush_cache_all();
>> +		outer_flush_all();
>
> You should only need to flush range on address 0-4 here.

ok.

Thanks,
Michal
Rob Herring March 25, 2013, 10:10 p.m. UTC | #3
On 03/25/2013 11:31 AM, Michal Simek wrote:
> On 03/25/2013 03:16 PM, Rob Herring wrote:
>> On 03/25/2013 08:53 AM, Michal Simek wrote:
>>> Zynq is dual core Cortex A9 which starts always
>>> at zero. Using simple trampoline ensure long jump
>>> to secondary_startup code.
>>>
>>> Signed-off-by: Michal Simek <michal.simek@xilinx.com>
>>> ---
>>>   arch/arm/mach-zynq/Makefile  |    1 +
>>>   arch/arm/mach-zynq/common.c  |    1 +
>>>   arch/arm/mach-zynq/common.h  |    7 ++
>>>   arch/arm/mach-zynq/platsmp.c |  160
>>> ++++++++++++++++++++++++++++++++++++++++++
>>>   arch/arm/mach-zynq/slcr.c    |   29 ++++++++
>>>   5 files changed, 198 insertions(+)
>>>   create mode 100644 arch/arm/mach-zynq/platsmp.c

[...]

>>> +}
>>> +
>>> +int __cpuinit zynq_cpun_start(u32 address, int cpu)
>>> +{
>>> +    if (cpu > ncores) {
>>> +        pr_warn("CPU No. is not available in the system\n");
>>> +        return -1;
>>> +    }
>>> +
>>> +    /* MS: Expectation that SLCR are directly map and accessible */
>>> +    /* Not possible to jump to non aligned address */
>>> +    if (!(address & 3) && (!address || (address >= 0xC))) {
>>
>> What about Thumb2 kernel entry?
> 
> I have no idea what's that.
> Still more microblaze guy than Arm one.

It's the 16-bit (mostly) instruction mode. Why it matters here is bit 0
being set in an address will trigger a switch to Thumb mode in a bx/blx
instruction. So you can't really check for alignment as only 0x2 would
not be allowed. More below...

> 
>>
>>> +        slcr_cpu_stop(cpu);
>>
>> Isn't a secondary cpu already stopped?
> 
> On the normal boot this is really necessary because first stage bootloader
> doesn't stop cpu just keep it in loop and without stopping cpu
> and starting it again it doesn't work.

And there is no way to exit the loop other than a reset?

So for for hotplug this would not be needed. Perhaps .smp_prepare_cpus
is a better spot for this.

If you can change the bootloader, then you should look at doing PSCI
support. Here's some information:

http://lca-13.zerista.com/files_user/attachments/9311/psci_update.pdf

I've also submitted highbank patches which add support for PSCI.

>>> +
>>> +        /*
>>> +         * This is elegant way how to jump to any address
>>> +         * 0x0: Load address at 0x8 to r0
>>> +         * 0x4: Jump by mov instruction
>>> +         * 0x8: Jumping address
>>> +         */
>>> +        if (address) {
>>> +            /* 0: ldr r0, [8] */
>>> +            __raw_writel(0xe59f0000, phys_to_virt(0x0));
>>> +            /* 4: mov pc, r0 */
>>> +            __raw_writel(0xe1a0f000, phys_to_virt(0x4));

This should be a "bx r0" to work with Thumb2 entry address.

Also, this part of the setup could be one time rather than every hotplug.

>>> +            __raw_writel(address, phys_to_virt(0x8));

This should be a per core address including core 0 if you ever want to
do something like cpuidle powergating on one core and hotplug on another.

Rob
Michal Simek March 26, 2013, 7:42 a.m. UTC | #4
2013/3/25 Rob Herring <robherring2@gmail.com>:
> On 03/25/2013 11:31 AM, Michal Simek wrote:
>> On 03/25/2013 03:16 PM, Rob Herring wrote:
>>> On 03/25/2013 08:53 AM, Michal Simek wrote:
>>>> Zynq is dual core Cortex A9 which starts always
>>>> at zero. Using simple trampoline ensure long jump
>>>> to secondary_startup code.
>>>>
>>>> Signed-off-by: Michal Simek <michal.simek@xilinx.com>
>>>> ---
>>>>   arch/arm/mach-zynq/Makefile  |    1 +
>>>>   arch/arm/mach-zynq/common.c  |    1 +
>>>>   arch/arm/mach-zynq/common.h  |    7 ++
>>>>   arch/arm/mach-zynq/platsmp.c |  160
>>>> ++++++++++++++++++++++++++++++++++++++++++
>>>>   arch/arm/mach-zynq/slcr.c    |   29 ++++++++
>>>>   5 files changed, 198 insertions(+)
>>>>   create mode 100644 arch/arm/mach-zynq/platsmp.c
>
> [...]
>
>>>> +}
>>>> +
>>>> +int __cpuinit zynq_cpun_start(u32 address, int cpu)
>>>> +{
>>>> +    if (cpu > ncores) {
>>>> +        pr_warn("CPU No. is not available in the system\n");
>>>> +        return -1;
>>>> +    }
>>>> +
>>>> +    /* MS: Expectation that SLCR are directly map and accessible */
>>>> +    /* Not possible to jump to non aligned address */
>>>> +    if (!(address & 3) && (!address || (address >= 0xC))) {
>>>
>>> What about Thumb2 kernel entry?
>>
>> I have no idea what's that.
>> Still more microblaze guy than Arm one.
>
> It's the 16-bit (mostly) instruction mode. Why it matters here is bit 0
> being set in an address will trigger a switch to Thumb mode in a bx/blx
> instruction. So you can't really check for alignment as only 0x2 would
> not be allowed. More below...

ok.


>
>>
>>>
>>>> +        slcr_cpu_stop(cpu);
>>>
>>> Isn't a secondary cpu already stopped?
>>
>> On the normal boot this is really necessary because first stage bootloader
>> doesn't stop cpu just keep it in loop and without stopping cpu
>> and starting it again it doesn't work.
>
> And there is no way to exit the loop other than a reset?

You can exit the loop by writing jump address to one location where bootloader
expect it. Then it jumps to proper function and it was done like that before.
But this is not suitable for cpu hotplug because that loop is placed
in OCM (on chip memory)
and it can be used for different purpose.
Also there is no way how to return cpu to this mode.

> So for for hotplug this would not be needed. Perhaps .smp_prepare_cpus
> is a better spot for this.

I don't think so because smp_prepare_cpus is called by the kernel just once
in bootup time.
For cpu hotplug I see that only smp_boot_secondary is called.


> If you can change the bootloader, then you should look at doing PSCI
> support. Here's some information:

Unfortunately no.

>
> http://lca-13.zerista.com/files_user/attachments/9311/psci_update.pdf
>
> I've also submitted highbank patches which add support for PSCI.

Interesting. I will look at it. Thanks.


>>>> +
>>>> +        /*
>>>> +         * This is elegant way how to jump to any address
>>>> +         * 0x0: Load address at 0x8 to r0
>>>> +         * 0x4: Jump by mov instruction
>>>> +         * 0x8: Jumping address
>>>> +         */
>>>> +        if (address) {
>>>> +            /* 0: ldr r0, [8] */
>>>> +            __raw_writel(0xe59f0000, phys_to_virt(0x0));
>>>> +            /* 4: mov pc, r0 */
>>>> +            __raw_writel(0xe1a0f000, phys_to_virt(0x4));
>
> This should be a "bx r0" to work with Thumb2 entry address.
>
> Also, this part of the setup could be one time rather than every hotplug.

It is better to copy it there all the time because we have one AMP demo
where rtos runs from 0x0 because there are reset vectors for it.
That's why it is necessary to copy this trampoline code all the time.


>>>> +            __raw_writel(address, phys_to_virt(0x8));
>
> This should be a per core address including core 0 if you ever want to
> do something like cpuidle powergating on one core and hotplug on another.

That's interesting idea.
Please correct me if I am wrong, I didn't play with cpuidle.

Zynq is dual core and hotplug can be done only on cpu1. (not sure if
in general cpu0
can be unplugged too. If yes, are you doing that?).
I didn't play with cpuidle code but I am not quite sure if you can use hotplug
if cpu0 is in idle because this code is for >cpu0.

I can imagine to be more flexible on quad core where your comment make
definitely sense.

Thanks,
Michal
Rob Herring April 1, 2013, 10:40 p.m. UTC | #5
On 03/26/2013 02:42 AM, Michal Simek wrote:
> 2013/3/25 Rob Herring <robherring2@gmail.com>:
>> On 03/25/2013 11:31 AM, Michal Simek wrote:
>>> On 03/25/2013 03:16 PM, Rob Herring wrote:
>>>> On 03/25/2013 08:53 AM, Michal Simek wrote:
>>>>> Zynq is dual core Cortex A9 which starts always
>>>>> at zero. Using simple trampoline ensure long jump
>>>>> to secondary_startup code.

[...]

>>>>> +        slcr_cpu_stop(cpu);
>>>>
>>>> Isn't a secondary cpu already stopped?
>>>
>>> On the normal boot this is really necessary because first stage bootloader
>>> doesn't stop cpu just keep it in loop and without stopping cpu
>>> and starting it again it doesn't work.
>>
>> And there is no way to exit the loop other than a reset?
> 
> You can exit the loop by writing jump address to one location where bootloader
> expect it. Then it jumps to proper function and it was done like that before.
> But this is not suitable for cpu hotplug because that loop is placed
> in OCM (on chip memory)
> and it can be used for different purpose.
> Also there is no way how to return cpu to this mode.

I was asking about the cold boot case, not hotplug. If you are spinning
in the bootloader waiting for a jump address, then why do you need the
slcr_cpu_stop for cold boot? In the hotplug case, you have already
called slcr_cpu_stop in the unplug path, so this shouldn't be needed
there either.

[...]

>>>>> +            __raw_writel(address, phys_to_virt(0x8));
>>
>> This should be a per core address including core 0 if you ever want to
>> do something like cpuidle powergating on one core and hotplug on another.
> 
> That's interesting idea.
> Please correct me if I am wrong, I didn't play with cpuidle.
> 
> Zynq is dual core and hotplug can be done only on cpu1. (not sure if
> in general cpu0
> can be unplugged too. If yes, are you doing that?).

You cannot do hotplug on cpu 0, but that is a current Linux limitation.
I believe support to hotplug cpu 0 was recently added for x86. So this
may change at some point.

What the h/w can support is another issue. Some chips have independent
power domains per core and some have combined domains. I can and do
powergate individual cores including core 0 in cpuidle for highbank. The
main difference to hotplug is whether I set the wake-up address when I
go down (cpuidle) or when I wake the core (hotplug). For hotplug, you
don't want the core to come back before the kernel is ready for it.

> I didn't play with cpuidle code but I am not quite sure if you can use hotplug
> if cpu0 is in idle because this code is for >cpu0.
> 
> I can imagine to be more flexible on quad core where your comment make
> definitely sense.

The next part could be quad core...

Rob
Michal Simek April 3, 2013, 6:44 a.m. UTC | #6
2013/4/2 Rob Herring <robherring2@gmail.com>:
> On 03/26/2013 02:42 AM, Michal Simek wrote:
>> 2013/3/25 Rob Herring <robherring2@gmail.com>:
>>> On 03/25/2013 11:31 AM, Michal Simek wrote:
>>>> On 03/25/2013 03:16 PM, Rob Herring wrote:
>>>>> On 03/25/2013 08:53 AM, Michal Simek wrote:
>>>>>> Zynq is dual core Cortex A9 which starts always
>>>>>> at zero. Using simple trampoline ensure long jump
>>>>>> to secondary_startup code.
>
> [...]
>
>>>>>> +        slcr_cpu_stop(cpu);
>>>>>
>>>>> Isn't a secondary cpu already stopped?
>>>>
>>>> On the normal boot this is really necessary because first stage bootloader
>>>> doesn't stop cpu just keep it in loop and without stopping cpu
>>>> and starting it again it doesn't work.
>>>
>>> And there is no way to exit the loop other than a reset?
>>
>> You can exit the loop by writing jump address to one location where bootloader
>> expect it. Then it jumps to proper function and it was done like that before.
>> But this is not suitable for cpu hotplug because that loop is placed
>> in OCM (on chip memory)
>> and it can be used for different purpose.
>> Also there is no way how to return cpu to this mode.
>
> I was asking about the cold boot case, not hotplug. If you are spinning
> in the bootloader waiting for a jump address, then why do you need the
> slcr_cpu_stop for cold boot? In the hotplug case, you have already
> called slcr_cpu_stop in the unplug path, so this shouldn't be needed
> there either.

For cold boot case there is an option to write jump addr
to specific address which fsbl (xilinx proprietary bootloader) expects
or just do cpu reset.
Waiting loop is placed in OCM. Then u-boot runs and user can change OCM mapping
for whatever reason.

My point is that reset is cleaner way because I am exactly sure
that cpu will be in the correct state and previous bootloader/code just
hasn't done any weird setting/changes.
It means that my requirement is that any bootloader will just ensure
that cpu1 is
in any waiting loop/sleep/whatever and I will do proper reset when I
need to start it.

I understand your concern about moving slcr_cpu_stop to different location
or even just stop cpu in bootloader but that's even more problematic.

>>>>>> +            __raw_writel(address, phys_to_virt(0x8));
>>>
>>> This should be a per core address including core 0 if you ever want to
>>> do something like cpuidle powergating on one core and hotplug on another.
>>
>> That's interesting idea.
>> Please correct me if I am wrong, I didn't play with cpuidle.
>>
>> Zynq is dual core and hotplug can be done only on cpu1. (not sure if
>> in general cpu0
>> can be unplugged too. If yes, are you doing that?).
>
> You cannot do hotplug on cpu 0, but that is a current Linux limitation.
> I believe support to hotplug cpu 0 was recently added for x86. So this
> may change at some point.
>
> What the h/w can support is another issue. Some chips have independent
> power domains per core and some have combined domains. I can and do
> powergate individual cores including core 0 in cpuidle for highbank. The
> main difference to hotplug is whether I set the wake-up address when I
> go down (cpuidle) or when I wake the core (hotplug). For hotplug, you
> don't want the core to come back before the kernel is ready for it.

ok

>> I didn't play with cpuidle code but I am not quite sure if you can use hotplug
>> if cpu0 is in idle because this code is for >cpu0.
>>
>> I can imagine to be more flexible on quad core where your comment make
>> definitely sense.
>
> The next part could be quad core...

I will look at changing this boot up code for future.

Thanks,
Michal
diff mbox

Patch

diff --git a/arch/arm/mach-zynq/Makefile b/arch/arm/mach-zynq/Makefile
index 13ee09b..8493a89 100644
--- a/arch/arm/mach-zynq/Makefile
+++ b/arch/arm/mach-zynq/Makefile
@@ -4,3 +4,4 @@ 
 
 # Common support
 obj-y				:= common.o slcr.o
+obj-$(CONFIG_SMP)		+= platsmp.o
diff --git a/arch/arm/mach-zynq/common.c b/arch/arm/mach-zynq/common.c
index 292f775..a58f9d6 100644
--- a/arch/arm/mach-zynq/common.c
+++ b/arch/arm/mach-zynq/common.c
@@ -103,6 +103,7 @@  static const char *xilinx_dt_match[] = {
 };
 
 MACHINE_START(XILINX_EP107, "Xilinx Zynq Platform")
+	.smp		= smp_ops(zynq_smp_ops),
 	.map_io		= xilinx_map_io,
 	.init_irq	= irqchip_init,
 	.init_machine	= xilinx_init_machine,
diff --git a/arch/arm/mach-zynq/common.h b/arch/arm/mach-zynq/common.h
index e5628f7..84145fb 100644
--- a/arch/arm/mach-zynq/common.h
+++ b/arch/arm/mach-zynq/common.h
@@ -19,6 +19,13 @@ 
 
 extern int slcr_init(void);
 extern void slcr_system_reset(void);
+extern void slcr_cpu_stop(int cpu);
+extern void slcr_cpu_start(int cpu);
+
+#ifdef CONFIG_SMP
+extern void secondary_startup(void);
+#endif
+extern struct smp_operations zynq_smp_ops __initdata;
 
 extern void __iomem *zynq_slcr_base;
 extern void __iomem *scu_base;
diff --git a/arch/arm/mach-zynq/platsmp.c b/arch/arm/mach-zynq/platsmp.c
new file mode 100644
index 0000000..d31ef72
--- /dev/null
+++ b/arch/arm/mach-zynq/platsmp.c
@@ -0,0 +1,160 @@ 
+/*
+ * This file contains Xilinx specific SMP code, used to start up
+ * the second processor.
+ *
+ * Copyright (C) 2011 - 2013 Xilinx
+ *
+ * based on linux/arch/arm/mach-realview/platsmp.c
+ *
+ * Copyright (C) 2002 ARM Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/export.h>
+#include <linux/jiffies.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <asm/cacheflush.h>
+#include <asm/smp_scu.h>
+#include <linux/irqchip/arm-gic.h>
+#include "common.h"
+
+static DEFINE_SPINLOCK(boot_lock);
+
+/*
+ * Store number of cores in the system
+ * Because of scu_get_core_count() must be in __init section and can't
+ * be called from zynq_cpun_start() because it is in __cpuinit section.
+ */
+static int ncores;
+
+/* Secondary CPU kernel startup is a 2 step process. The primary CPU
+ * starts the secondary CPU by giving it the address of the kernel and
+ * then sending it an event to wake it up. The secondary CPU then
+ * starts the kernel and tells the primary CPU it's up and running.
+ */
+static void __cpuinit zynq_secondary_init(unsigned int cpu)
+{
+	/*
+	 * if any interrupts are already enabled for the primary
+	 * core (e.g. timer irq), then they will not have been enabled
+	 * for us: do so
+	 */
+	gic_secondary_init(0);
+
+	/*
+	 * Synchronise with the boot thread.
+	 */
+	spin_lock(&boot_lock);
+	spin_unlock(&boot_lock);
+}
+
+int __cpuinit zynq_cpun_start(u32 address, int cpu)
+{
+	if (cpu > ncores) {
+		pr_warn("CPU No. is not available in the system\n");
+		return -1;
+	}
+
+	/* MS: Expectation that SLCR are directly map and accessible */
+	/* Not possible to jump to non aligned address */
+	if (!(address & 3) && (!address || (address >= 0xC))) {
+		slcr_cpu_stop(cpu);
+
+		/*
+		 * This is elegant way how to jump to any address
+		 * 0x0: Load address at 0x8 to r0
+		 * 0x4: Jump by mov instruction
+		 * 0x8: Jumping address
+		 */
+		if (address) {
+			/* 0: ldr r0, [8] */
+			__raw_writel(0xe59f0000, phys_to_virt(0x0));
+			/* 4: mov pc, r0 */
+			__raw_writel(0xe1a0f000, phys_to_virt(0x4));
+			__raw_writel(address, phys_to_virt(0x8));
+		}
+
+		flush_cache_all();
+		outer_flush_all();
+		wmb();
+
+		slcr_cpu_start(cpu);
+
+		return 0;
+	}
+
+	pr_warn("Can't start CPU%d: Wrong starting address %x\n", cpu, address);
+
+	return -1;
+}
+EXPORT_SYMBOL(zynq_cpun_start);
+
+static int __cpuinit zynq_boot_secondary(unsigned int cpu,
+						struct task_struct *idle)
+{
+	int ret;
+
+	/*
+	 * set synchronisation state between this boot processor
+	 * and the secondary one
+	 */
+	spin_lock(&boot_lock);
+
+	ret = zynq_cpun_start(virt_to_phys(secondary_startup), cpu);
+	if (ret) {
+		spin_unlock(&boot_lock);
+		return -1;
+	}
+
+	/*
+	 * now the secondary core is starting up let it run its
+	 * calibrations, then wait for it to finish
+	 */
+	spin_unlock(&boot_lock);
+
+	return 0;
+}
+
+/*
+ * Initialise the CPU possible map early - this describes the CPUs
+ * which may be present or become present in the system.
+ */
+static void __init zynq_smp_init_cpus(void)
+{
+	int i;
+
+	ncores = scu_get_core_count(scu_base);
+
+	for (i = 0; i < ncores && i < CONFIG_NR_CPUS; i++)
+		set_cpu_possible(i, true);
+}
+
+static void __init zynq_smp_prepare_cpus(unsigned int max_cpus)
+{
+	int i;
+
+	/*
+	 * Initialise the present map, which describes the set of CPUs
+	 * actually populated at the present time.
+	 */
+	for (i = 0; i < max_cpus; i++)
+		set_cpu_present(i, true);
+
+	scu_enable(scu_base);
+}
+
+struct smp_operations zynq_smp_ops __initdata = {
+	.smp_init_cpus		= zynq_smp_init_cpus,
+	.smp_prepare_cpus	= zynq_smp_prepare_cpus,
+	.smp_secondary_init	= zynq_secondary_init,
+	.smp_boot_secondary	= zynq_boot_secondary,
+};
diff --git a/arch/arm/mach-zynq/slcr.c b/arch/arm/mach-zynq/slcr.c
index 36b79d8..7f2a919 100644
--- a/arch/arm/mach-zynq/slcr.c
+++ b/arch/arm/mach-zynq/slcr.c
@@ -33,6 +33,11 @@ 
 #define SLCR_UNLOCK			0x8   /* SCLR unlock register */
 
 #define SLCR_PSS_RST_CTRL_OFFSET	0x200 /* PS Software Reset Control */
+
+#define SLCR_A9_CPU_CLKSTOP		0x10
+#define SLCR_A9_CPU_RST			0x1
+
+#define SLCR_A9_CPU_RST_CTRL		0x244 /* CPU Software Reset Control */
 #define SLCR_REBOOT_STATUS		0x258 /* PS Reboot Status */
 
 void __iomem *zynq_slcr_base;
@@ -61,6 +66,30 @@  void slcr_system_reset(void)
 }
 
 /**
+ * slcr_cpu_start - Start cpu
+ * @cpu:	cpu number
+ */
+void slcr_cpu_start(int cpu)
+{
+	/* enable CPUn */
+	writel(SLCR_A9_CPU_CLKSTOP << cpu,
+	       zynq_slcr_base + SLCR_A9_CPU_RST_CTRL);
+	/* enable CLK for CPUn */
+	writel(0x0 << cpu, zynq_slcr_base + SLCR_A9_CPU_RST_CTRL);
+}
+
+/**
+ * slcr_cpu_stop - Stop cpu
+ * @cpu:	cpu number
+ */
+void slcr_cpu_stop(int cpu)
+{
+	/* stop CLK and reset CPUn */
+	writel((SLCR_A9_CPU_CLKSTOP | SLCR_A9_CPU_RST) << cpu,
+	       zynq_slcr_base + SLCR_A9_CPU_RST_CTRL);
+}
+
+/**
  * xslcr_init()
  * Returns 0 on success, negative errno otherwise.
  *