[Galaxy S II Original Android Development] zeitferne:
Regarding "it scales cpu0": Both CPUs share the same HW register, so it scales both.

The clock register should be EXYNOS4_CLKDIV_CPU and I already logged its contents (note these are hex values):

<0>[25361.818397] c0 CLKDIV_CPU: 77143737; CLKDIV_CPU1: 50

Your comment prompted me to decipher this value, and this is indeed the value that exynos_enter_idle writes.

Using the #defines / constants at arch/arm/mach-exynos/include/mach/regs-clock.h I deciphered this to the following:

CLKDIV_CPU: core=7, corem0=3, corem1=7, periph=3, atb=4, pclkdbg=1, apll=7, core2=7

CLKDIV_CPU1: acppclk (wrongly called DIVCOPY in the clkdiv_cpu1 table) = 5.

Most of these values correspond to the entries for 1200 MHz, as would be right considering the previous call to exynos4210_set_frequency. However, one values is off: the core=7 should be =0. Guess what? This is one of the two values written there by exynos_enter_idle! The other value written by it is core2=7 which also matches.

How is the 150 MHz calculated?

It is the value of the clock "armclk". This clock is defined in arch/arm/mach-exynos/clock-exynos4.c to be equal to "core_clk" which is backed by the hardware register EXYNOS4_CLKDIV_CPU (yes, the one from above!). The 150 MHz are calculated by reading the clock divider (clkdiv) from the register and dividing the rate of the parent clock through it. The clock divider is read from the core=7 part, but before using that value, 1 is added to it (presumably because dividing by 0 makes no sense, 0 is used to mean 1 and so on). This means we get 150 MHz = parent clock rate / 8. The parent clock (also to be found in clock-exynos4.c) is moutcore, and it's rate (also in the log) is 1200000000KHz. Check! (I found the described formula in the s3c_getrate_clksrc function in arch/arm/plat-samsung/clock-clksrc.c.)

Next question: exynos4_enter_idle should restore this value upon leaving (and the code clearly does that)! What went wrong here? Could it be that the function was aborted by shutting CPU1 down while it was running there?

How exynos4_enter_idle works:

This function is called on every CPU once it is idling (i.e. got nothing to do). In the function, first CPU-local interrupts are disabled (local_irq_disable). This means that the code runs uninterrupted, but code running on the other CPU could still run concurrently. Next, the check for SW_CLK_DWN is always true on our phone. By calling get_cpu() it obtains the CPU id (0 or 1) and then enters a spin lock: No other thread may enter that same lock ("idle_lock") concurrently, which is important because the other processor might still be running and we don't want to get global variables clobbered by threads calling the same function there. The "cpu_core |= (1 << cpu);" line uses the global integer variable cpu_core as a bit set. A set bit 0 indicates that CPU0 is currently in exynos4_enter_idle, a set bit 1 is the same for CPU1. Thus the next if checks if all enabled CPUs are in exynos4_enter_idle (either both CPUs are idling (0x3 = 11 in binary) or the secondary CPU is disabled). If so, it writes to the aforementioned EXYNOS4_CLKDIV_CPU register to reduce power consumption.

After the if, it leaves the spinlock and waits until it gets something to do in cpu_do_idle(); (could it be that the CPU can be shut down there?).

Now comes only cleanup: Using the same logic as above it checks if all running CPUs are idling and if so restores EXYNOS4_CLKDIV_CPU (I guess due to disabled interrupts it is impossible for the second CPU to come online before this check? Because that would lead to skipping the restoration of EXYNOS4_CLKDIV_CPU.) and then unsets the bit for this cpu in the cpu_core global (both things while holding the aforementioned idle_lock) and finally re-enables interrupts.