Loading...
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 168 169 170 171 172 173 174 | // SPDX-License-Identifier: GPL-2.0 /* * arch/sh/kernel/cpu/sh4/clock-sh4-202.c * * Additional SH4-202 support for the clock framework * * Copyright (C) 2005 Paul Mundt */ #include <linux/init.h> #include <linux/kernel.h> #include <linux/err.h> #include <linux/io.h> #include <linux/clkdev.h> #include <asm/clock.h> #include <asm/freq.h> #define CPG2_FRQCR3 0xfe0a0018 static int frqcr3_divisors[] = { 1, 2, 3, 4, 6, 8, 16 }; static int frqcr3_values[] = { 0, 1, 2, 3, 4, 5, 6 }; static unsigned long emi_clk_recalc(struct clk *clk) { int idx = __raw_readl(CPG2_FRQCR3) & 0x0007; return clk->parent->rate / frqcr3_divisors[idx]; } static inline int frqcr3_lookup(struct clk *clk, unsigned long rate) { int divisor = clk->parent->rate / rate; int i; for (i = 0; i < ARRAY_SIZE(frqcr3_divisors); i++) if (frqcr3_divisors[i] == divisor) return frqcr3_values[i]; /* Safe fallback */ return 5; } static struct sh_clk_ops sh4202_emi_clk_ops = { .recalc = emi_clk_recalc, }; static struct clk sh4202_emi_clk = { .flags = CLK_ENABLE_ON_INIT, .ops = &sh4202_emi_clk_ops, }; static unsigned long femi_clk_recalc(struct clk *clk) { int idx = (__raw_readl(CPG2_FRQCR3) >> 3) & 0x0007; return clk->parent->rate / frqcr3_divisors[idx]; } static struct sh_clk_ops sh4202_femi_clk_ops = { .recalc = femi_clk_recalc, }; static struct clk sh4202_femi_clk = { .flags = CLK_ENABLE_ON_INIT, .ops = &sh4202_femi_clk_ops, }; static void shoc_clk_init(struct clk *clk) { int i; /* * For some reason, the shoc_clk seems to be set to some really * insane value at boot (values outside of the allowable frequency * range for instance). We deal with this by scaling it back down * to something sensible just in case. * * Start scaling from the high end down until we find something * that passes rate verification.. */ for (i = 0; i < ARRAY_SIZE(frqcr3_divisors); i++) { int divisor = frqcr3_divisors[i]; if (clk->ops->set_rate(clk, clk->parent->rate / divisor) == 0) break; } WARN_ON(i == ARRAY_SIZE(frqcr3_divisors)); /* Undefined clock */ } static unsigned long shoc_clk_recalc(struct clk *clk) { int idx = (__raw_readl(CPG2_FRQCR3) >> 6) & 0x0007; return clk->parent->rate / frqcr3_divisors[idx]; } static int shoc_clk_verify_rate(struct clk *clk, unsigned long rate) { struct clk *bclk = clk_get(NULL, "bus_clk"); unsigned long bclk_rate = clk_get_rate(bclk); clk_put(bclk); if (rate > bclk_rate) return 1; if (rate > 66000000) return 1; return 0; } static int shoc_clk_set_rate(struct clk *clk, unsigned long rate) { unsigned long frqcr3; unsigned int tmp; /* Make sure we have something sensible to switch to */ if (shoc_clk_verify_rate(clk, rate) != 0) return -EINVAL; tmp = frqcr3_lookup(clk, rate); frqcr3 = __raw_readl(CPG2_FRQCR3); frqcr3 &= ~(0x0007 << 6); frqcr3 |= tmp << 6; __raw_writel(frqcr3, CPG2_FRQCR3); clk->rate = clk->parent->rate / frqcr3_divisors[tmp]; return 0; } static struct sh_clk_ops sh4202_shoc_clk_ops = { .init = shoc_clk_init, .recalc = shoc_clk_recalc, .set_rate = shoc_clk_set_rate, }; static struct clk sh4202_shoc_clk = { .flags = CLK_ENABLE_ON_INIT, .ops = &sh4202_shoc_clk_ops, }; static struct clk *sh4202_onchip_clocks[] = { &sh4202_emi_clk, &sh4202_femi_clk, &sh4202_shoc_clk, }; static struct clk_lookup lookups[] = { /* main clocks */ CLKDEV_CON_ID("emi_clk", &sh4202_emi_clk), CLKDEV_CON_ID("femi_clk", &sh4202_femi_clk), CLKDEV_CON_ID("shoc_clk", &sh4202_shoc_clk), }; int __init arch_clk_init(void) { struct clk *clk; int i, ret = 0; cpg_clk_init(); clk = clk_get(NULL, "master_clk"); for (i = 0; i < ARRAY_SIZE(sh4202_onchip_clocks); i++) { struct clk *clkp = sh4202_onchip_clocks[i]; clkp->parent = clk; ret |= clk_register(clkp); } clk_put(clk); clkdev_add_table(lookups, ARRAY_SIZE(lookups)); return ret; } |