S3C2410 and cpufreq

Cesar Eduardo Barros cesarb at cesarb.net
Sat Feb 16 13:01:30 CET 2008


For some weeks I have been working on a cpufreq driver for the OpenMoko 
Neo1973 GTA01, which uses a S3C2410 SoC. It's far from complete (it will 
end up touching almost every device driver for the SoC), but the core 
code (which does the frequency switching) is mostly ready.[1]

Some characteristics of the S3C2410 are not well supported by the core 
cpufreq architecture, meaning it had to be bent a bit to fit. Harald 
Welte on openmoko-kernel suggested I ask for comments on this list (I've 
added a CC: to openmoko-kernel so they can follow the discussion).

- The S3C2410 and other chips derived from it have three main clocks, 
FCLK, HCLK, and PCLK. HCLK is obtained from FCLK via a divider, and PCLK 
is obtained from HCLK via another divider. It's FCLK which tracks the 
PLL output. All the devices on the chip use either HCLK or PCLK as their 
clock, except the CPU core, which uses FCLK (and on the S3C2442, used by 
the GTA02, the CPU core can use the HCLK instead of FCLK).[2]

This means almost all device drivers for the devices on the chip will 
need (or work better with) a cpufreq transition notifier. However, what 
they are interested in is not the CPU core frequency (which doesn't 
interest them at all), but one of the three main clocks.[3]

I solved this by embedding struct cpufreq_freqs inside another 
structure, which has the old and new values for all the relevant clocks. 
However, if the transition notifier is called from the cpufreq core 
without being asked to by the cpufreq driver (should never happen in 
practice, only in theory), the pointer passed to the notifiers is not 
embedded within the bigger structure, meaning I had to pass it out of 
band.[4]

- The cpufreq core allows the drivers to set a range of valid 
frequencies (policy->min and policy->max); however, the drivers often 
want to just exclude some frequencies from the middle of the range, and 
shrinking the range is too restrictive.

One of the reasons is that the relation between the CPU core frequency 
and the main clocks is not monotonic; for instance, we might have 
HCLK=FCLK/2 at higher frequencies, but then change to HCLK=FCLK at lower 
frequencies (where we can do so without getting above the maximum HCLK 
frequency). The drivers might not like a HCLK or PCLK below a minimum, 
but often what the cpufreq core is tracking is FCLK (or sometimes HCLK 
on the S3C2442).

Another reason is the serial driver. In some frequencies, none of the 
available divider values on the baud rate generator can generate a 
frequency within the tolerances for the desired baud rate, especially on 
the higher baud rates (like the 115200 used by the GTA01 modem). These 
frequencies are not only the lower ones, but also a few of the higher 
ones. The driver needs a way to exclude these frequencies, without 
halving the whole range.

The way I solved this is a bit of a hack. I use a separate frequency 
table to mark which frequencies are available or not.[5] At the 
beginning of the CPUFREQ_ADJUST phase[6], the table is cleared (in fact 
copied from a "clean" table). During the rest of the phase, the drivers 
call a special exported helper function, passing it a callback. The 
helper function generates the clock values for all the frequencies, and 
ask the callback whether they can be used. Based on the returned boolean 
value, the function can mark the frequency as invalid on the table, and 
if needed shrink the policy range. Despite being a bit verbose for the 
drivers (which need to declare and register a notifier block, and 
implement both the policy notifier and the callback), it pushes all the 
complexity to the cpufreq driver.

- There is more than one way to set a particular CPU core frequency, 
since the dividers for the other clocks can vary.

Following a suggestion on IRC, I implemented this by enhancing the 
policy adjust helper described above; it now also iterates through all 
the valid frequencies for that CPU core clock, asking the driver whether 
any of them is valid, and stopping at the first found. This is usually 
the slowest possible (higher divider values), except for a special 
performance hack: if this is the frequency which would be chosen by the 
"performance" governor, I only allow the fastest divider values.[7]

- Some drivers do not want a frequency switch in some situations.

One example would be the sound driver[8], where the transition latencies 
could cause dropped audio samples. Another example would be the USB 
gadget driver[8], since for some reason it stops working if in use 
during a frequency transition.

The way I planned to work around this is to have another exported helper 
function which sets a global boolean flag. At the end of the 
CPUFREQ_INCOMPATIBLE phase[9], if the flag is set, it sets policy->min 
to policy->max and clears the flag. This function is to be called by the 
relevant drivers on their policy notifiers during the CPUFREQ_ADJUST phase.


[1] The current revision of the code can be found at the 
s3c2410-cpufreq-om branch of 
http://repo.or.cz/w/linux-2.6/s3c2410-cpufreq.git; the revision of the 
code current as of this email message is the one sent to the 
openmoko-kernel mailing list, archived at 
http://lists.openmoko.org/pipermail/openmoko-kernel/2008-February/001021.html 
and its replies.
[2] I simplified a bit; there's another PLL which generates 48MHz for 
the USB, and a few devices can use it (mostly the USB cores).
[3] Or in some cases more than one of the three main clocks; the serial 
driver on the S3C2442 can use either PCLK or a divided FCLK.
[4] A global pointer to the struct on the stack is set before starting 
the transition and cleared after ending it; there's an exported function 
to get it. To the drivers, it's as if that function did a container_of 
on the passed pointer; they are unaware of the global pointer, other 
than the fact that they will receive NULL if it's not available.
[5] This table is later directly passed to cpufreq_frequency_table_target().
[6] Implemented via a policy notifier with a very high priority.
[7] This is actually done when clearing the table of valid divider 
values, at the beginning of the CPUFREQ_ADJUST phase. For the 
"performance" index, all the other divider values are pre-set to invalid.
[8] Not adapted for cpufreq yet.
[9] Implemented via a policy notifier with a very low (negative) priority.

-- 
Cesar Eduardo Barros
cesarb at cesarb.net
cesar.barros at gmail.com




More information about the openmoko-kernel mailing list