summaryrefslogtreecommitdiff
path: root/drivers/clk/sunxi/clk-sunxi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk/sunxi/clk-sunxi.c')
-rw-r--r--drivers/clk/sunxi/clk-sunxi.c164
1 files changed, 138 insertions, 26 deletions
diff --git a/drivers/clk/sunxi/clk-sunxi.c b/drivers/clk/sunxi/clk-sunxi.c
index 9c0dc7438774..1f02b028db48 100644
--- a/drivers/clk/sunxi/clk-sunxi.c
+++ b/drivers/clk/sunxi/clk-sunxi.c
@@ -297,50 +297,162 @@ static void sun4i_get_pll1_factors(u32 *freq, u32 parent_rate,
* parent_rate should always be 24MHz
*/
static void sun6i_a31_get_pll1_factors(u32 *freq, u32 parent_rate,
- u8 *n, u8 *k, u8 *m, u8 *p)
+ u8 *n, u8 *k, u8 *m, u8 *p)
{
- const u32 parent_mhz = 24;
+ const u8 pll_base_count = 11; /* number of primes till 32 */
+ const u8 pll_primes[11] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31};
+ const u32 parent_mhz = 24; /* always 24 */
+ const u8 f0[11] = {3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}; /* 2^3 * 3^1 */
const u8 N_max = 32; /* 5 bits */
const u8 K_max = 4; /* 2 bits */
const u8 M_max = 4; /* 2 bits */
- u8 N, K, M; /* multiplicators (e.g. N = n+1) */
- u32 freq_mhz, steps_mhz, NxK;
+ u8 N = 1, K = 1, M = 1; /* multiplicators (e.g. N = n+1) */
+ u32 freq_mhz, tmp;
+ u8 success;
+ u8 ft[11]; /* target frequency will be factorized here */
+ int i;
freq_mhz = *freq / 1000000;
- /* We will try to get the maximum resolution */
- if (freq_mhz < parent_mhz*N_max*K_max/4)
- M = 4;
- else if (freq_mhz < parent_mhz*N_max*K_max/3)
- M = 3;
- else if (freq_mhz < parent_mhz*N_max*K_max/2)
- M = 2;
- else
- M = 1;
-
- steps_mhz = parent_mhz / M;
+ /* minimum possible frequency is 6 */
+ if (freq_mhz<6)
+ freq_mhz = 6;
+
+ success = 0;
+ /* try to factorize; if it fails, try with next lower number */
+ while (!success) {
+ memset(&ft, 0x00, pll_base_count);
+ tmp = freq_mhz;
+ for (i=0; i<pll_base_count; i++) {
+ if (tmp<pll_primes[i])
+ break;
+ while (tmp%pll_primes[i]==0) {
+ tmp /= pll_primes[i];
+ ft[i]++;
+ }
+ }
+ /* we could not factorize (remainder is too large) */
+ if (tmp!=1) {
+ freq_mhz--;
+ continue;
+ }
+ /* because M can be either 1, 2, 3 or 4, we cannot get numbers which
+ * don't follow the conditions below (in the factorized form) */
+ if (ft[1]>=1 && ft[0]<1) {
+ freq_mhz--;
+ continue;
+ }
+ if (ft[1]==0 && ft[0]<3) {
+ freq_mhz--;
+ continue;
+ }
+ /* of course, it might be that the number is not obtainable with the
+ * current registers, but that should be pretty rare and we cannot
+ * really tell in this phase; there is another check below */
+ success = 1;
+ }
- NxK = freq_mhz / steps_mhz;
+ /* get the N, K and M values based on the factors; for each prime number,
+ * try to get the required exponent and to adjust M, K and N to reach it */
+ for (i=pll_base_count-1; i>=0; i--) {
+ int target = ft[i]-f0[i];
+ /* required exponent from N, K and M; if it's 0, there is nothing to do
+ * for this prime number, we move on */
+ if (target==0)
+ continue;
+ /* we try to set the divider and multiplier as to achieve the desired
+ * exponent; we always check if the value does not go over the maximum
+ * values for multipliers */
+ /* if target is lower than zero, we must use the divider M */
+ if (target<0)
+ while (target<0 && M*pll_primes[i]<=M_max) {
+ target++;
+ M *= pll_primes[i];
+ }
+ /* otherwise, we use multipliers K and N (in this order) */
+ else {
+ while (target>0 && K*pll_primes[i]<=K_max) {
+ target--;
+ K *= pll_primes[i];
+ }
+ while (target>0 && N*pll_primes[i]<=N_max) {
+ target--;
+ N *= pll_primes[i];
+ }
+ }
+ }
- /* We try to keep K as low as possible */
- if (freq_mhz <= steps_mhz*N_max*1)
- K = 1;
- else if (freq_mhz <= steps_mhz*N_max*2)
- K = 2;
- else if (freq_mhz <= steps_mhz*N_max*3)
- K = 3;
- else
- K = 4;
+ /* have we succeeded? if not, we call the function again with the next
+ * lower frequency */
+ if (parent_mhz*N*K/M != freq_mhz) {
+ *freq -= 1000000;
+ sun6i_a31_get_pll1_factors(freq, parent_rate, n, k, m, p);
+ return;
+ }
- N = NxK / K;
+ /* some tweaks to improve pll stability */
+ /* The pll can be quite tricky with tranzitions; these tweaks are based on
+ * empirical observations on the behaviour of the pll */
+ /* M cannot be too low */
+ switch (M) {
+ case 1: {
+ u8 factor = 1;
+ if (N*4 <= N_max)
+ factor = 4;
+ else if (N*3 <= N_max)
+ factor = 2;
+ else if (N*2 <= N_max)
+ factor = 2;
+ M *= factor;
+ N *= factor;
+ break;
+ }
+ case 2:
+ if (N*2 <= N_max) {
+ M *= 2;
+ N *= 2;
+ }
+ break;
+ }
+ /* K cannot be too low either */
+ switch (K) {
+ case 1: {
+ u8 factor = 1;
+ if (N%4 == 0)
+ factor = 4;
+ else if (N%3 == 0)
+ factor = 3;
+ else if (N%2 == 0)
+ factor = 2;
+ K *= factor;
+ N /= factor;
+ break;
+ }
+ case 2:
+ if (N%2 == 0) {
+ K *= 2;
+ N /= 2;
+ }
+ break;
+ }
+ /* 3 is the best value ever! */
+ if (K==M && K!=3)
+ K = M = 3;
+ /* 4 is better than 2, 2 is better than 1 */
+ else if (M<3 && K<3) {
+ M *= 2;
+ K *= 2;
+ }
*freq = (parent_rate * N * K) / M;
+ /* finally, setting the register values based on N, K and M */
if (n != NULL) {
*n = N-1;
*k = K-1;
*m = M-1;
}
+
}
/**