1 /* 2 * Clock and power management. This exposes the combined functionality 3 * provided by the jz4740 and related SoCs. The power management 4 * functionality could be exposed using a separate driver. 5 * 6 * Copyright (C) 2017, 2018 Paul Boddie <paul@boddie.org.uk> 7 * 8 * This program is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU General Public License as 10 * published by the Free Software Foundation; either version 2 of 11 * the License, or (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 * Boston, MA 02110-1301, USA 22 */ 23 24 #include <l4/devices/hw_mmio_register_block.h> 25 #include "cpm-jz4740.h" 26 27 28 29 enum Regs : unsigned 30 { 31 Clock_control = 0x000, // CPCCR 32 Low_power_control = 0x004, // LCR 33 Pll_control = 0x010, // CPPCR 34 Clock_gate = 0x020, // CLKGR 35 Sleep_control = 0x024, // SCR 36 I2s_divider = 0x060, // I2SCDR 37 Lcd_divider = 0x064, // LPCDR 38 Msc_divider = 0x068, // MSCCDR 39 Uhc_divider = 0x06c, // UHCCDR 40 Ssi_divider = 0x074, // SSICDR 41 }; 42 43 enum Clock_bits : unsigned 44 { 45 Clock_enable = 22, // CE 46 Clock_pllout_source = 21, // PCS 47 Clock_lcd_divider = 16, // LCD 48 Clock_memory_divider = 12, // MDIV 49 Clock_pclock_divider = 8, // PDIV (slow APB peripherals) 50 Clock_hclock_divider = 4, // HDIV (fast AHB peripherals) 51 Clock_cpu_divider = 0, // CDIV 52 }; 53 54 enum Pll_bits : unsigned 55 { 56 Pll_multiplier = 23, // PLLM 57 Pll_input_division = 18, // PLLN 58 Pll_output_division = 16, // PLLOD 59 Pll_stable = 10, // PLLS 60 Pll_bypassed = 9, // PLLBP 61 Pll_enabled = 8, // PLLEN 62 }; 63 64 enum Clock_gate_bits : unsigned 65 { 66 Clock_gate_lcd = 10, // LCD 67 Clock_gate_timer = 1, // TCU 68 }; 69 70 enum Lcd_divider_bits : unsigned 71 { 72 Lcd_divider_value = 0, 73 }; 74 75 76 77 // If implemented as a Hw::Device, various properties would be 78 // initialised in the constructor and obtained from the device tree 79 // definitions. 80 81 Cpm_jz4740_chip::Cpm_jz4740_chip(l4_addr_t addr, uint32_t exclk_freq) 82 : _exclk_freq(exclk_freq) 83 { 84 _regs = new Hw::Mmio_register_block<32>(addr); 85 86 // add_cid("cpm"); 87 // add_cid("cpm-jz4740"); 88 // register_property("exclk_freq", &_exclk_freq); 89 } 90 91 // Clock/timer control. 92 93 int 94 Cpm_jz4740_chip::have_clock() 95 { 96 return !(_regs[Clock_gate] & (1 << Clock_gate_timer)); 97 } 98 99 void 100 Cpm_jz4740_chip::start_clock() 101 { 102 _regs[Clock_gate] = _regs[Clock_gate] & ~(1 << Clock_gate_timer); 103 } 104 105 // PLL control. 106 107 // Return whether the PLL is stable. 108 109 int 110 Cpm_jz4740_chip::have_pll() 111 { 112 return _regs[Pll_control] & (1 << Pll_stable); 113 } 114 115 int 116 Cpm_jz4740_chip::pll_enabled() 117 { 118 return _regs[Pll_control] & (1 << Pll_enabled); 119 } 120 121 int 122 Cpm_jz4740_chip::pll_bypassed() 123 { 124 return _regs[Pll_control] & (1 << Pll_bypassed); 125 } 126 127 // Feedback (9-bit) multiplier. 128 129 uint16_t 130 Cpm_jz4740_chip::get_multiplier() 131 { 132 return ((_regs[Pll_control] & (0x1ff << Pll_multiplier)) >> Pll_multiplier) + 2; 133 } 134 135 // Input (5-bit) divider. 136 137 uint8_t 138 Cpm_jz4740_chip::get_input_division() 139 { 140 return ((_regs[Pll_control] & (0x1f << Pll_input_division)) >> Pll_input_division) + 2; 141 } 142 143 // Output divider. 144 145 uint8_t 146 Cpm_jz4740_chip::get_output_division() 147 { 148 uint8_t od[4] = {1, 2, 2, 4}; 149 return od[(_regs[Pll_control] & (0x03 << Pll_output_division)) >> Pll_output_division]; 150 } 151 152 // General clock divider. 153 154 uint8_t 155 Cpm_jz4740_chip::_get_divider(uint32_t reg, uint32_t mask, uint8_t shift) 156 { 157 uint8_t cd[] = {1, 2, 3, 4, 6, 8, 12, 16, 24, 32}; 158 uint8_t d = (_regs[reg] & mask) >> shift; 159 return (d < 10) ? cd[d] : 1; 160 } 161 162 // CPU clock (CCLK) divider. 163 164 uint8_t 165 Cpm_jz4740_chip::get_cpu_divider() 166 { 167 return _get_divider(Clock_control, 0xf << Clock_cpu_divider, Clock_cpu_divider); 168 } 169 170 // Fast peripheral clock (HCLK) divider. 171 172 uint8_t 173 Cpm_jz4740_chip::get_hclock_divider() 174 { 175 return _get_divider(Clock_control, 0xf << Clock_hclock_divider, Clock_hclock_divider); 176 } 177 178 // Slow peripheral clock (PCLK) divider. 179 180 uint8_t 181 Cpm_jz4740_chip::get_pclock_divider() 182 { 183 return _get_divider(Clock_control, 0xf << Clock_pclock_divider, Clock_pclock_divider); 184 } 185 186 // Memory clock (MCLK) divider. 187 188 uint8_t 189 Cpm_jz4740_chip::get_memory_divider() 190 { 191 return _get_divider(Clock_control, 0xf << Clock_memory_divider, Clock_memory_divider); 192 } 193 194 // Clock source divider for MSC, I2S, LCD and USB. 195 196 uint8_t 197 Cpm_jz4740_chip::get_source_divider() 198 { 199 return _regs[Clock_control] & (1 << Clock_pllout_source) ? 1 : 2; 200 } 201 202 // LCD device clock divider. 203 204 void 205 Cpm_jz4740_chip::set_lcd_device_divider(uint8_t division) 206 { 207 if (division == 0) 208 division = 1; 209 else if (division > 32) 210 division = 32; 211 212 _regs[Clock_control] = (_regs[Clock_control] & ~(0x1f << Clock_lcd_divider)) | 213 ((division - 1) << Clock_lcd_divider); 214 } 215 216 // LCD pixel clock divider. 217 218 uint16_t 219 Cpm_jz4740_chip::get_lcd_pixel_divider() 220 { 221 return (_regs[Lcd_divider] >> Lcd_divider_value) + 1; 222 } 223 224 void 225 Cpm_jz4740_chip::set_lcd_pixel_divider(uint16_t division) 226 { 227 if (division == 0) 228 division = 1; 229 else if (division > 2048) 230 division = 2048; 231 232 _regs[Lcd_divider] = (_regs[Lcd_divider] & ~(0x7ff << Lcd_divider_value)) | 233 ((division - 1) << Lcd_divider_value); 234 } 235 236 237 238 uint32_t 239 Cpm_jz4740_chip::get_lcd_pixel_frequency() 240 { 241 return get_output_frequency() / get_lcd_pixel_divider(); 242 } 243 244 // Set the device and pixel frequencies, indicating the latter and 245 // providing the device:pixel frequency ratio. 246 247 void 248 Cpm_jz4740_chip::set_lcd_frequencies(uint32_t pclk, uint8_t ratio) 249 { 250 uint32_t out = get_output_frequency(), 251 lcd = pclk * ratio; 252 253 set_lcd_pixel_divider(out / pclk); 254 255 // Limit the device frequency to 150MHz. 256 257 if (lcd > 150000000) lcd = 150000000; 258 259 set_lcd_device_divider(out / lcd); 260 } 261 262 263 264 // LCD clock control. 265 266 void 267 Cpm_jz4740_chip::start_lcd() 268 { 269 _regs[Clock_gate] = _regs[Clock_gate] & ~(1 << Clock_gate_lcd); 270 } 271 272 void 273 Cpm_jz4740_chip::stop_lcd() 274 { 275 _regs[Clock_gate] = _regs[Clock_gate] | (1 << Clock_gate_lcd); 276 } 277 278 279 280 uint32_t 281 Cpm_jz4740_chip::get_pll_frequency() 282 { 283 // Test for PLL enable and not PLL bypass. 284 285 if (pll_enabled() && !pll_bypassed()) 286 return (_exclk_freq * get_multiplier()) / 287 (get_input_division() * get_output_division()); 288 else 289 return _exclk_freq; 290 } 291 292 // Clock frequency for MSC, I2S, LCD and USB. 293 294 uint32_t 295 Cpm_jz4740_chip::get_output_frequency() 296 { 297 return get_pll_frequency() / get_source_divider(); 298 } 299 300 void 301 Cpm_jz4740_chip::update_output_frequency() 302 { 303 _regs[Clock_control] = _regs[Clock_control] | (1 << Clock_enable); 304 } 305 306 // Clock frequency for the CPU. 307 308 uint32_t Cpm_jz4740_chip::get_cpu_frequency() 309 { 310 return get_pll_frequency() / get_cpu_divider(); 311 } 312 313 // Clock frequency for fast peripherals. 314 315 uint32_t 316 Cpm_jz4740_chip::get_hclock_frequency() 317 { 318 return get_pll_frequency() / get_hclock_divider(); 319 } 320 321 // Clock frequency for slow peripherals. 322 323 uint32_t 324 Cpm_jz4740_chip::get_pclock_frequency() 325 { 326 return get_pll_frequency() / get_pclock_divider(); 327 } 328 329 // Clock frequency for the memory. 330 331 uint32_t 332 Cpm_jz4740_chip::get_memory_frequency() 333 { 334 return get_pll_frequency() / get_memory_divider(); 335 } 336 337 338 339 // C language interface functions. 340 341 void 342 *jz4740_cpm_init(l4_addr_t cpm_base) 343 { 344 /* Initialise the clock and power management peripheral with the 345 register memory region and a 12MHz EXCLK frequency. */ 346 347 return (void *) new Cpm_jz4740_chip(cpm_base, 12000000); 348 } 349 350 int 351 jz4740_cpm_have_clock(void *cpm) 352 { 353 return static_cast<Cpm_jz4740_chip *>(cpm)->have_clock(); 354 } 355 356 void 357 jz4740_cpm_start_clock(void *cpm) 358 { 359 static_cast<Cpm_jz4740_chip *>(cpm)->start_clock(); 360 } 361 362 void 363 jz4740_cpm_start_lcd(void *cpm) 364 { 365 static_cast<Cpm_jz4740_chip *>(cpm)->start_lcd(); 366 } 367 368 void 369 jz4740_cpm_stop_lcd(void *cpm) 370 { 371 static_cast<Cpm_jz4740_chip *>(cpm)->stop_lcd(); 372 } 373 374 uint32_t 375 jz4740_cpm_get_cpu_frequency(void *cpm) 376 { 377 return static_cast<Cpm_jz4740_chip *>(cpm)->get_cpu_frequency(); 378 } 379 380 uint32_t 381 jz4740_cpm_get_hclock_frequency(void *cpm) 382 { 383 return static_cast<Cpm_jz4740_chip *>(cpm)->get_hclock_frequency(); 384 } 385 386 uint32_t 387 jz4740_cpm_get_output_frequency(void *cpm) 388 { 389 return static_cast<Cpm_jz4740_chip *>(cpm)->get_output_frequency(); 390 } 391 392 uint32_t 393 jz4740_cpm_get_pclock_frequency(void *cpm) 394 { 395 return static_cast<Cpm_jz4740_chip *>(cpm)->get_pclock_frequency(); 396 } 397 398 uint32_t 399 jz4740_cpm_get_memory_frequency(void *cpm) 400 { 401 return static_cast<Cpm_jz4740_chip *>(cpm)->get_memory_frequency(); 402 } 403 404 uint16_t 405 jz4740_cpm_get_lcd_pixel_divider(void *cpm) 406 { 407 return static_cast<Cpm_jz4740_chip *>(cpm)->get_lcd_pixel_divider(); 408 } 409 410 uint32_t 411 jz4740_cpm_get_lcd_pixel_frequency(void *cpm) 412 { 413 return static_cast<Cpm_jz4740_chip *>(cpm)->get_lcd_pixel_frequency(); 414 } 415 416 void 417 jz4740_cpm_set_lcd_frequencies(void *cpm, uint32_t pclk, uint8_t ratio) 418 { 419 static_cast<Cpm_jz4740_chip *>(cpm)->set_lcd_frequencies(pclk, ratio); 420 } 421 422 void 423 jz4740_cpm_update_output_frequency(void *cpm) 424 { 425 static_cast<Cpm_jz4740_chip *>(cpm)->update_output_frequency(); 426 }