paul@230 | 1 | /* |
paul@230 | 2 | * Clock and power management. |
paul@230 | 3 | * |
paul@230 | 4 | * Copyright (C) Xiangfu Liu <xiangfu@sharism.cc> |
paul@230 | 5 | * Copyright (C) 2015, 2016, 2017 Paul Boddie <paul@boddie.org.uk> |
paul@230 | 6 | * |
paul@230 | 7 | * This program is free software: you can redistribute it and/or modify |
paul@230 | 8 | * it under the terms of the GNU General Public License as published by |
paul@230 | 9 | * the Free Software Foundation, either version 3 of the License, or |
paul@230 | 10 | * (at your option) any later version. |
paul@230 | 11 | * |
paul@230 | 12 | * This program is distributed in the hope that it will be useful, |
paul@230 | 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
paul@230 | 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
paul@230 | 15 | * GNU General Public License for more details. |
paul@230 | 16 | * |
paul@230 | 17 | * You should have received a copy of the GNU General Public License |
paul@230 | 18 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
paul@230 | 19 | */ |
paul@230 | 20 | |
paul@230 | 21 | #include "xburst_types.h" |
paul@230 | 22 | #include "cpm.h" |
paul@230 | 23 | #include "board.h" |
paul@230 | 24 | |
paul@230 | 25 | static uint32_t cpm_ctrl_get(void *cpm_base, uint32_t reg) |
paul@230 | 26 | { |
paul@230 | 27 | return REG32(cpm_base + reg); |
paul@230 | 28 | } |
paul@230 | 29 | |
paul@230 | 30 | static void cpm_ctrl_set(void *cpm_base, uint32_t reg, uint32_t value) |
paul@230 | 31 | { |
paul@230 | 32 | REG32(cpm_base + reg) = value; |
paul@230 | 33 | } |
paul@230 | 34 | |
paul@230 | 35 | static int pll_enabled(void *cpm_base) |
paul@230 | 36 | { |
paul@230 | 37 | return cpm_ctrl_get(cpm_base, CPM_CPPCR) & CPM_CPPCR_PLLEN; |
paul@230 | 38 | } |
paul@230 | 39 | |
paul@230 | 40 | static int pll_bypassed(void *cpm_base) |
paul@230 | 41 | { |
paul@230 | 42 | return cpm_ctrl_get(cpm_base, CPM_CPPCR) & CPM_CPPCR_PLLBP; |
paul@230 | 43 | } |
paul@230 | 44 | |
paul@230 | 45 | // Feedback (9-bit) divider. |
paul@230 | 46 | |
paul@230 | 47 | static uint16_t get_multiplier(void *cpm_base) |
paul@230 | 48 | { |
paul@230 | 49 | return ((cpm_ctrl_get(cpm_base, CPM_CPPCR) & CPM_CPPCR_PLLM_MASK) >> CPM_CPPCR_PLLM_BIT) + 2; |
paul@230 | 50 | } |
paul@230 | 51 | |
paul@230 | 52 | // Input (5-bit) divider. |
paul@230 | 53 | |
paul@230 | 54 | static uint8_t get_input_divider(void *cpm_base) |
paul@230 | 55 | { |
paul@230 | 56 | return ((cpm_ctrl_get(cpm_base, CPM_CPPCR) & CPM_CPPCR_PLLN_MASK) >> CPM_CPPCR_PLLN_BIT) + 2; |
paul@230 | 57 | } |
paul@230 | 58 | |
paul@230 | 59 | // Output divider. |
paul@230 | 60 | |
paul@230 | 61 | static uint8_t get_output_divider(void *cpm_base) |
paul@230 | 62 | { |
paul@230 | 63 | uint8_t od[] = {1, 2, 2, 4}; |
paul@230 | 64 | return od[(cpm_ctrl_get(cpm_base, CPM_CPPCR) & CPM_CPPCR_PLLOD_MASK) >> CPM_CPPCR_PLLOD_BIT]; |
paul@230 | 65 | } |
paul@230 | 66 | |
paul@230 | 67 | // General clock divider. |
paul@230 | 68 | |
paul@230 | 69 | static uint8_t _get_divider(void *cpm_base, uint32_t reg, uint32_t mask, uint8_t shift) |
paul@230 | 70 | { |
paul@230 | 71 | uint8_t cd[] = {1, 2, 3, 4, 6, 8, 12, 16, 24, 32}; |
paul@230 | 72 | uint8_t d = (cpm_ctrl_get(cpm_base, reg) & mask) >> shift; |
paul@230 | 73 | return (d < 10) ? cd[d] : 1; |
paul@230 | 74 | } |
paul@230 | 75 | |
paul@230 | 76 | // CPU clock divider. |
paul@230 | 77 | |
paul@230 | 78 | static uint8_t get_cpu_divider(void *cpm_base) |
paul@230 | 79 | { |
paul@230 | 80 | return _get_divider(cpm_base, CPM_CPCCR, CPM_CPCCR_CDIV_MASK, CPM_CPCCR_CDIV_BIT); |
paul@230 | 81 | } |
paul@230 | 82 | |
paul@230 | 83 | // Memory clock divider. |
paul@230 | 84 | |
paul@230 | 85 | static uint8_t get_memory_divider(void *cpm_base) |
paul@230 | 86 | { |
paul@230 | 87 | return _get_divider(cpm_base, CPM_CPCCR, CPM_CPCCR_MDIV_MASK, CPM_CPCCR_MDIV_BIT); |
paul@230 | 88 | } |
paul@230 | 89 | |
paul@230 | 90 | // Clock source divider for MSC, I2S, LCD and USB. |
paul@230 | 91 | |
paul@230 | 92 | static uint8_t get_source_divider(void *cpm_base) |
paul@230 | 93 | { |
paul@230 | 94 | #ifdef CONFIG_CPU_JZ4730 |
paul@230 | 95 | return 1; |
paul@230 | 96 | #else |
paul@230 | 97 | return cpm_ctrl_get(cpm_base, CPM_CPCCR) & CPM_CPCCR_PCS ? 1 : 2; |
paul@230 | 98 | #endif |
paul@230 | 99 | } |
paul@230 | 100 | |
paul@230 | 101 | // LCD device clock divider. |
paul@230 | 102 | |
paul@230 | 103 | static void set_lcd_device_divider(void *cpm_base, uint8_t division) |
paul@230 | 104 | { |
paul@230 | 105 | if (division == 0) |
paul@230 | 106 | division = 1; |
paul@230 | 107 | #ifdef CONFIG_CPU_JZ4730 |
paul@230 | 108 | else if (division > 16) |
paul@230 | 109 | division = 16; |
paul@230 | 110 | #else |
paul@230 | 111 | else if (division > 32) |
paul@230 | 112 | division = 32; |
paul@230 | 113 | #endif |
paul@230 | 114 | |
paul@230 | 115 | cpm_ctrl_set(cpm_base, CPM_CPCCR, |
paul@230 | 116 | (cpm_ctrl_get(cpm_base, CPM_CPCCR) & ~CPM_CPCCR_LDIV_MASK) | |
paul@230 | 117 | ((division - 1) << CPM_CPCCR_LDIV_BIT)); |
paul@230 | 118 | } |
paul@230 | 119 | |
paul@230 | 120 | // LCD pixel clock divider. |
paul@230 | 121 | |
paul@230 | 122 | static void set_lcd_pixel_divider(void *cpm_base, uint16_t division) |
paul@230 | 123 | { |
paul@230 | 124 | #ifndef CONFIG_CPU_JZ4730 |
paul@230 | 125 | if (division == 0) |
paul@230 | 126 | division = 1; |
paul@230 | 127 | else if (division > 2048) |
paul@230 | 128 | division = 2048; |
paul@230 | 129 | |
paul@230 | 130 | cpm_ctrl_set(cpm_base, CPM_LPCDR, |
paul@230 | 131 | (cpm_ctrl_get(cpm_base, CPM_LPCDR) & ~CPM_LPCDR_PIXDIV_MASK) | |
paul@230 | 132 | (division - 1)); |
paul@230 | 133 | #endif |
paul@230 | 134 | } |
paul@230 | 135 | |
paul@230 | 136 | static uint32_t get_pll_frequency(void *cpm_base) |
paul@230 | 137 | { |
paul@230 | 138 | // Test for PLL enable and not PLL bypass. |
paul@230 | 139 | |
paul@230 | 140 | if (pll_enabled(cpm_base) && !pll_bypassed(cpm_base)) |
paul@230 | 141 | return (JZ_EXTAL * get_multiplier(cpm_base)) / |
paul@230 | 142 | (get_input_divider(cpm_base) * get_output_divider(cpm_base)); |
paul@230 | 143 | else |
paul@230 | 144 | return JZ_EXTAL; |
paul@230 | 145 | } |
paul@230 | 146 | |
paul@230 | 147 | // Clock frequency for MSC, I2S, LCD and USB. |
paul@230 | 148 | |
paul@230 | 149 | static uint32_t get_output_frequency(void *cpm_base) |
paul@230 | 150 | { |
paul@230 | 151 | return get_pll_frequency(cpm_base) / get_source_divider(cpm_base); |
paul@230 | 152 | } |
paul@230 | 153 | |
paul@230 | 154 | |
paul@230 | 155 | |
paul@230 | 156 | /* Public functions. */ |
paul@230 | 157 | |
paul@230 | 158 | // Clock frequency for the CPU. |
paul@230 | 159 | |
paul@230 | 160 | uint32_t jz4740_cpm_get_cpu_frequency(void *cpm_base) |
paul@230 | 161 | { |
paul@230 | 162 | return get_pll_frequency(cpm_base) / get_cpu_divider(cpm_base); |
paul@230 | 163 | } |
paul@230 | 164 | |
paul@230 | 165 | // Clock frequency for the memory. |
paul@230 | 166 | |
paul@230 | 167 | uint32_t jz4740_cpm_get_memory_frequency(void *cpm_base) |
paul@230 | 168 | { |
paul@230 | 169 | return get_pll_frequency(cpm_base) / get_memory_divider(cpm_base); |
paul@230 | 170 | } |
paul@230 | 171 | |
paul@230 | 172 | // Set the device and pixel frequencies, indicating the latter and |
paul@230 | 173 | // providing the device:pixel frequency ratio. |
paul@230 | 174 | |
paul@230 | 175 | void jz4740_cpm_set_lcd_frequencies(void *cpm_base, uint32_t pclk, uint8_t ratio) |
paul@230 | 176 | { |
paul@230 | 177 | uint32_t out = get_output_frequency(cpm_base), lcd = pclk * ratio; |
paul@230 | 178 | |
paul@230 | 179 | set_lcd_pixel_divider(cpm_base, out / pclk); |
paul@230 | 180 | |
paul@230 | 181 | // Limit the device frequency to 150MHz. |
paul@230 | 182 | |
paul@230 | 183 | if (lcd > 150000000) lcd = 150000000; |
paul@230 | 184 | |
paul@230 | 185 | set_lcd_device_divider(cpm_base, out / lcd); |
paul@230 | 186 | } |
paul@230 | 187 | |
paul@230 | 188 | // Update the clock output frequency. |
paul@230 | 189 | |
paul@230 | 190 | void jz4740_cpm_update_output_frequency(void *cpm_base) |
paul@230 | 191 | { |
paul@230 | 192 | cpm_ctrl_set(cpm_base, CPM_CPCCR, cpm_ctrl_get(cpm_base, CPM_CPCCR) | CPM_CPCCR_CE); |
paul@230 | 193 | } |
paul@230 | 194 | |
paul@230 | 195 | // General clock functions. |
paul@230 | 196 | |
paul@230 | 197 | int jz4740_cpm_have_clock(void *cpm_base) |
paul@230 | 198 | { |
paul@230 | 199 | // NOTE: To check. |
paul@230 | 200 | #ifdef CONFIG_CPU_JZ4730 |
paul@230 | 201 | return cpm_ctrl_get(cpm_base, CPM_MSCR) != 0; |
paul@230 | 202 | #else |
paul@230 | 203 | return cpm_ctrl_get(cpm_base, CPM_CLKGR) != 0; |
paul@230 | 204 | #endif |
paul@230 | 205 | } |
paul@230 | 206 | |
paul@230 | 207 | int jz4740_cpm_have_pll(void *cpm_base) |
paul@230 | 208 | { |
paul@230 | 209 | return cpm_ctrl_get(cpm_base, CPM_CPPCR) & CPM_CPPCR_PLLS; |
paul@230 | 210 | } |
paul@230 | 211 | |
paul@230 | 212 | void jz4740_cpm_start_clock(void *cpm_base) |
paul@230 | 213 | { |
paul@230 | 214 | #ifdef CONFIG_CPU_JZ4730 |
paul@230 | 215 | cpm_start_ost(cpm_base); |
paul@230 | 216 | cpm_ctrl_set(cpm_base, CPM_MSCR, cpm_ctrl_get(cpm_base, CPM_MSCR) & ~CPM_MSCR_MSTP_OST); |
paul@230 | 217 | #else |
paul@230 | 218 | cpm_ctrl_set(cpm_base, CPM_CLKGR, cpm_ctrl_get(cpm_base, CPM_CLKGR) & ~CPM_CLKGR_TCU); |
paul@230 | 219 | #endif |
paul@230 | 220 | } |
paul@230 | 221 | |
paul@230 | 222 | // Peripheral clock control. |
paul@230 | 223 | |
paul@230 | 224 | void jz4740_cpm_start_lcd(void *cpm_base) |
paul@230 | 225 | { |
paul@230 | 226 | #ifndef CONFIG_CPU_JZ4730 |
paul@230 | 227 | cpm_ctrl_set(cpm_base, CPM_CLKGR, cpm_ctrl_get(cpm_base, CPM_CLKGR) & ~CPM_CLKGR_LCD); |
paul@230 | 228 | #endif |
paul@230 | 229 | } |
paul@230 | 230 | |
paul@230 | 231 | void jz4740_cpm_stop_lcd(void *cpm_base) |
paul@230 | 232 | { |
paul@230 | 233 | #ifndef CONFIG_CPU_JZ4730 |
paul@230 | 234 | cpm_ctrl_set(cpm_base, CPM_CLKGR, cpm_ctrl_get(cpm_base, CPM_CLKGR) | CPM_CLKGR_LCD); |
paul@230 | 235 | #endif |
paul@230 | 236 | } |
paul@230 | 237 | |
paul@230 | 238 | // Register access. |
paul@230 | 239 | |
paul@230 | 240 | uint32_t jz4740_cpm_ctrl_get(void *cpm_base, uint32_t reg) |
paul@230 | 241 | { |
paul@230 | 242 | return cpm_ctrl_get(cpm_base, reg); |
paul@230 | 243 | } |
paul@230 | 244 | |
paul@230 | 245 | void jz4740_cpm_ctrl_set(void *cpm_base, uint32_t reg, uint32_t value) |
paul@230 | 246 | { |
paul@230 | 247 | cpm_ctrl_set(cpm_base, reg, value); |
paul@230 | 248 | } |
paul@230 | 249 | |
paul@230 | 250 | /* Top-level initialisation. */ |
paul@230 | 251 | |
paul@230 | 252 | void cpm_init() |
paul@230 | 253 | { |
paul@230 | 254 | #ifdef CONFIG_CPU_JZ4730 |
paul@230 | 255 | cpm_ctrl_set((void *) CPM_BASE, CPM_MSCR, 0xffffffff); |
paul@230 | 256 | #else |
paul@230 | 257 | cpm_ctrl_set((void *) CPM_BASE, CPM_CLKGR, 0x7fff); |
paul@230 | 258 | #endif |
paul@230 | 259 | } |
paul@230 | 260 | |