Kukjin Kim | 3109e55 | 2010-09-01 15:35:30 +0900 | [diff] [blame] | 1 | /* linux/arch/arm/mach-s5p64x0/clock.c |
| 2 | * |
| 3 | * Copyright (c) 2010 Samsung Electronics Co., Ltd. |
| 4 | * http://www.samsung.com |
| 5 | * |
| 6 | * S5P64X0 - Clock support |
| 7 | * |
| 8 | * This program is free software; you can redistribute it and/or modify |
| 9 | * it under the terms of the GNU General Public License version 2 as |
| 10 | * published by the Free Software Foundation. |
| 11 | */ |
| 12 | |
| 13 | #include <linux/init.h> |
| 14 | #include <linux/module.h> |
| 15 | #include <linux/kernel.h> |
| 16 | #include <linux/list.h> |
| 17 | #include <linux/errno.h> |
| 18 | #include <linux/err.h> |
| 19 | #include <linux/clk.h> |
Kay Sievers | edbaa60 | 2011-12-21 16:26:03 -0800 | [diff] [blame] | 20 | #include <linux/device.h> |
Kukjin Kim | 3109e55 | 2010-09-01 15:35:30 +0900 | [diff] [blame] | 21 | #include <linux/io.h> |
| 22 | |
| 23 | #include <mach/hardware.h> |
| 24 | #include <mach/map.h> |
| 25 | #include <mach/regs-clock.h> |
| 26 | |
| 27 | #include <plat/cpu-freq.h> |
| 28 | #include <plat/clock.h> |
| 29 | #include <plat/cpu.h> |
| 30 | #include <plat/pll.h> |
| 31 | #include <plat/s5p-clock.h> |
| 32 | #include <plat/clock-clksrc.h> |
Kukjin Kim | 95af214 | 2011-12-22 23:28:28 +0100 | [diff] [blame] | 33 | |
| 34 | #include "common.h" |
Kukjin Kim | 3109e55 | 2010-09-01 15:35:30 +0900 | [diff] [blame] | 35 | |
| 36 | struct clksrc_clk clk_mout_apll = { |
| 37 | .clk = { |
| 38 | .name = "mout_apll", |
| 39 | .id = -1, |
| 40 | }, |
| 41 | .sources = &clk_src_apll, |
| 42 | .reg_src = { .reg = S5P64X0_CLK_SRC0, .shift = 0, .size = 1 }, |
| 43 | }; |
| 44 | |
| 45 | struct clksrc_clk clk_mout_mpll = { |
| 46 | .clk = { |
| 47 | .name = "mout_mpll", |
| 48 | .id = -1, |
| 49 | }, |
| 50 | .sources = &clk_src_mpll, |
| 51 | .reg_src = { .reg = S5P64X0_CLK_SRC0, .shift = 1, .size = 1 }, |
| 52 | }; |
| 53 | |
| 54 | struct clksrc_clk clk_mout_epll = { |
| 55 | .clk = { |
| 56 | .name = "mout_epll", |
| 57 | .id = -1, |
| 58 | }, |
| 59 | .sources = &clk_src_epll, |
| 60 | .reg_src = { .reg = S5P64X0_CLK_SRC0, .shift = 2, .size = 1 }, |
| 61 | }; |
| 62 | |
| 63 | enum perf_level { |
| 64 | L0 = 532*1000, |
| 65 | L1 = 266*1000, |
| 66 | L2 = 133*1000, |
| 67 | }; |
| 68 | |
| 69 | static const u32 clock_table[][3] = { |
| 70 | /*{ARM_CLK, DIVarm, DIVhclk}*/ |
| 71 | {L0 * 1000, (0 << ARM_DIV_RATIO_SHIFT), (3 << S5P64X0_CLKDIV0_HCLK_SHIFT)}, |
| 72 | {L1 * 1000, (1 << ARM_DIV_RATIO_SHIFT), (1 << S5P64X0_CLKDIV0_HCLK_SHIFT)}, |
| 73 | {L2 * 1000, (3 << ARM_DIV_RATIO_SHIFT), (0 << S5P64X0_CLKDIV0_HCLK_SHIFT)}, |
| 74 | }; |
| 75 | |
Kukjin Kim | 3109e55 | 2010-09-01 15:35:30 +0900 | [diff] [blame] | 76 | unsigned long s5p64x0_armclk_get_rate(struct clk *clk) |
| 77 | { |
| 78 | unsigned long rate = clk_get_rate(clk->parent); |
| 79 | u32 clkdiv; |
| 80 | |
| 81 | /* divisor mask starts at bit0, so no need to shift */ |
| 82 | clkdiv = __raw_readl(ARM_CLK_DIV) & ARM_DIV_MASK; |
| 83 | |
| 84 | return rate / (clkdiv + 1); |
| 85 | } |
| 86 | |
| 87 | unsigned long s5p64x0_armclk_round_rate(struct clk *clk, unsigned long rate) |
| 88 | { |
| 89 | u32 iter; |
| 90 | |
| 91 | for (iter = 1 ; iter < ARRAY_SIZE(clock_table) ; iter++) { |
| 92 | if (rate > clock_table[iter][0]) |
| 93 | return clock_table[iter-1][0]; |
| 94 | } |
| 95 | |
| 96 | return clock_table[ARRAY_SIZE(clock_table) - 1][0]; |
| 97 | } |
| 98 | |
| 99 | int s5p64x0_armclk_set_rate(struct clk *clk, unsigned long rate) |
| 100 | { |
| 101 | u32 round_tmp; |
| 102 | u32 iter; |
| 103 | u32 clk_div0_tmp; |
| 104 | u32 cur_rate = clk->ops->get_rate(clk); |
| 105 | unsigned long flags; |
| 106 | |
| 107 | round_tmp = clk->ops->round_rate(clk, rate); |
| 108 | if (round_tmp == cur_rate) |
| 109 | return 0; |
| 110 | |
| 111 | |
| 112 | for (iter = 0 ; iter < ARRAY_SIZE(clock_table) ; iter++) { |
| 113 | if (round_tmp == clock_table[iter][0]) |
| 114 | break; |
| 115 | } |
| 116 | |
| 117 | if (iter >= ARRAY_SIZE(clock_table)) |
| 118 | iter = ARRAY_SIZE(clock_table) - 1; |
| 119 | |
| 120 | local_irq_save(flags); |
| 121 | if (cur_rate > round_tmp) { |
| 122 | /* Frequency Down */ |
| 123 | clk_div0_tmp = __raw_readl(ARM_CLK_DIV) & ~(ARM_DIV_MASK); |
| 124 | clk_div0_tmp |= clock_table[iter][1]; |
| 125 | __raw_writel(clk_div0_tmp, ARM_CLK_DIV); |
| 126 | |
| 127 | clk_div0_tmp = __raw_readl(ARM_CLK_DIV) & |
| 128 | ~(S5P64X0_CLKDIV0_HCLK_MASK); |
| 129 | clk_div0_tmp |= clock_table[iter][2]; |
| 130 | __raw_writel(clk_div0_tmp, ARM_CLK_DIV); |
| 131 | |
| 132 | |
| 133 | } else { |
| 134 | /* Frequency Up */ |
| 135 | clk_div0_tmp = __raw_readl(ARM_CLK_DIV) & |
| 136 | ~(S5P64X0_CLKDIV0_HCLK_MASK); |
| 137 | clk_div0_tmp |= clock_table[iter][2]; |
| 138 | __raw_writel(clk_div0_tmp, ARM_CLK_DIV); |
| 139 | |
| 140 | clk_div0_tmp = __raw_readl(ARM_CLK_DIV) & ~(ARM_DIV_MASK); |
| 141 | clk_div0_tmp |= clock_table[iter][1]; |
| 142 | __raw_writel(clk_div0_tmp, ARM_CLK_DIV); |
| 143 | } |
| 144 | local_irq_restore(flags); |
| 145 | |
| 146 | clk->rate = clock_table[iter][0]; |
| 147 | |
| 148 | return 0; |
| 149 | } |
| 150 | |
| 151 | struct clk_ops s5p64x0_clkarm_ops = { |
| 152 | .get_rate = s5p64x0_armclk_get_rate, |
| 153 | .set_rate = s5p64x0_armclk_set_rate, |
| 154 | .round_rate = s5p64x0_armclk_round_rate, |
| 155 | }; |
| 156 | |
| 157 | struct clksrc_clk clk_armclk = { |
| 158 | .clk = { |
| 159 | .name = "armclk", |
| 160 | .id = 1, |
| 161 | .parent = &clk_mout_apll.clk, |
| 162 | .ops = &s5p64x0_clkarm_ops, |
| 163 | }, |
| 164 | .reg_div = { .reg = S5P64X0_CLK_DIV0, .shift = 0, .size = 4 }, |
| 165 | }; |
| 166 | |
| 167 | struct clksrc_clk clk_dout_mpll = { |
| 168 | .clk = { |
| 169 | .name = "dout_mpll", |
| 170 | .id = -1, |
| 171 | .parent = &clk_mout_mpll.clk, |
| 172 | }, |
| 173 | .reg_div = { .reg = S5P64X0_CLK_DIV0, .shift = 4, .size = 1 }, |
| 174 | }; |
| 175 | |
| 176 | struct clk *clkset_hclk_low_list[] = { |
| 177 | &clk_mout_apll.clk, |
| 178 | &clk_mout_mpll.clk, |
| 179 | }; |
| 180 | |
| 181 | struct clksrc_sources clkset_hclk_low = { |
| 182 | .sources = clkset_hclk_low_list, |
| 183 | .nr_sources = ARRAY_SIZE(clkset_hclk_low_list), |
| 184 | }; |
| 185 | |
| 186 | int s5p64x0_pclk_ctrl(struct clk *clk, int enable) |
| 187 | { |
| 188 | return s5p_gatectrl(S5P64X0_CLK_GATE_PCLK, clk, enable); |
| 189 | } |
| 190 | |
| 191 | int s5p64x0_hclk0_ctrl(struct clk *clk, int enable) |
| 192 | { |
| 193 | return s5p_gatectrl(S5P64X0_CLK_GATE_HCLK0, clk, enable); |
| 194 | } |
| 195 | |
| 196 | int s5p64x0_hclk1_ctrl(struct clk *clk, int enable) |
| 197 | { |
| 198 | return s5p_gatectrl(S5P64X0_CLK_GATE_HCLK1, clk, enable); |
| 199 | } |
| 200 | |
| 201 | int s5p64x0_sclk_ctrl(struct clk *clk, int enable) |
| 202 | { |
| 203 | return s5p_gatectrl(S5P64X0_CLK_GATE_SCLK0, clk, enable); |
| 204 | } |
| 205 | |
| 206 | int s5p64x0_sclk1_ctrl(struct clk *clk, int enable) |
| 207 | { |
| 208 | return s5p_gatectrl(S5P64X0_CLK_GATE_SCLK1, clk, enable); |
| 209 | } |
| 210 | |
| 211 | int s5p64x0_mem_ctrl(struct clk *clk, int enable) |
| 212 | { |
| 213 | return s5p_gatectrl(S5P64X0_CLK_GATE_MEM0, clk, enable); |
| 214 | } |
| 215 | |
| 216 | int s5p64x0_clk48m_ctrl(struct clk *clk, int enable) |
| 217 | { |
| 218 | unsigned long flags; |
| 219 | u32 val; |
| 220 | |
| 221 | /* can't rely on clock lock, this register has other usages */ |
| 222 | local_irq_save(flags); |
| 223 | |
| 224 | val = __raw_readl(S5P64X0_OTHERS); |
| 225 | if (enable) |
| 226 | val |= S5P64X0_OTHERS_USB_SIG_MASK; |
| 227 | else |
| 228 | val &= ~S5P64X0_OTHERS_USB_SIG_MASK; |
| 229 | |
| 230 | __raw_writel(val, S5P64X0_OTHERS); |
| 231 | |
| 232 | local_irq_restore(flags); |
| 233 | |
| 234 | return 0; |
| 235 | } |