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, 2021 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 uint32_t 94 Cpm_jz4740_chip::get_clock_gate_value(enum Clock_identifiers clock) 95 { 96 switch (clock) 97 { 98 case Clock_lcd: return (1 << Clock_gate_lcd); 99 case Clock_timer: return (1 << Clock_gate_timer); 100 default: return 0; 101 } 102 } 103 104 int 105 Cpm_jz4740_chip::have_clock(enum Clock_identifiers clock) 106 { 107 return !(_regs[Clock_gate] & get_clock_gate_value(clock)); 108 } 109 110 void 111 Cpm_jz4740_chip::start_clock(enum Clock_identifiers clock) 112 { 113 _regs[Clock_gate] = _regs[Clock_gate] & ~get_clock_gate_value(clock); 114 } 115 116 void 117 Cpm_jz4740_chip::stop_clock(enum Clock_identifiers clock) 118 { 119 _regs[Clock_gate] = _regs[Clock_gate] | get_clock_gate_value(clock); 120 } 121 122 // PLL control. 123 124 // Return whether the PLL is stable. 125 126 int 127 Cpm_jz4740_chip::have_pll() 128 { 129 return _regs[Pll_control] & (1 << Pll_stable); 130 } 131 132 int 133 Cpm_jz4740_chip::pll_enabled() 134 { 135 return _regs[Pll_control] & (1 << Pll_enabled); 136 } 137 138 int 139 Cpm_jz4740_chip::pll_bypassed() 140 { 141 return _regs[Pll_control] & (1 << Pll_bypassed); 142 } 143 144 // Feedback (9-bit) multiplier. 145 146 uint16_t 147 Cpm_jz4740_chip::get_multiplier() 148 { 149 return ((_regs[Pll_control] & (0x1ff << Pll_multiplier)) >> Pll_multiplier) + 2; 150 } 151 152 // Input (5-bit) divider. 153 154 uint8_t 155 Cpm_jz4740_chip::get_input_division() 156 { 157 return ((_regs[Pll_control] & (0x1f << Pll_input_division)) >> Pll_input_division) + 2; 158 } 159 160 // Output divider. 161 162 uint8_t 163 Cpm_jz4740_chip::get_output_division() 164 { 165 uint8_t od[4] = {1, 2, 2, 4}; 166 return od[(_regs[Pll_control] & (0x03 << Pll_output_division)) >> Pll_output_division]; 167 } 168 169 // General clock divider. 170 171 uint8_t 172 Cpm_jz4740_chip::_get_divider(uint32_t reg, uint32_t mask, uint8_t shift) 173 { 174 uint8_t cd[] = {1, 2, 3, 4, 6, 8, 12, 16, 24, 32}; 175 uint8_t d = (_regs[reg] & mask) >> shift; 176 return (d < 10) ? cd[d] : 1; 177 } 178 179 // CPU clock (CCLK) divider. 180 181 uint8_t 182 Cpm_jz4740_chip::get_cpu_divider() 183 { 184 return _get_divider(Clock_control, 0xf << Clock_cpu_divider, Clock_cpu_divider); 185 } 186 187 // Fast peripheral clock (HCLK) divider. 188 189 uint8_t 190 Cpm_jz4740_chip::get_hclock_divider() 191 { 192 return _get_divider(Clock_control, 0xf << Clock_hclock_divider, Clock_hclock_divider); 193 } 194 195 // Slow peripheral clock (PCLK) divider. 196 197 uint8_t 198 Cpm_jz4740_chip::get_pclock_divider() 199 { 200 return _get_divider(Clock_control, 0xf << Clock_pclock_divider, Clock_pclock_divider); 201 } 202 203 // Memory clock (MCLK) divider. 204 205 uint8_t 206 Cpm_jz4740_chip::get_memory_divider() 207 { 208 return _get_divider(Clock_control, 0xf << Clock_memory_divider, Clock_memory_divider); 209 } 210 211 // Clock source divider for MSC, I2S, LCD and USB. 212 213 uint8_t 214 Cpm_jz4740_chip::get_source_divider() 215 { 216 return _regs[Clock_control] & (1 << Clock_pllout_source) ? 1 : 2; 217 } 218 219 // LCD device clock divider. 220 221 void 222 Cpm_jz4740_chip::set_lcd_device_divider(uint8_t division) 223 { 224 if (division == 0) 225 division = 1; 226 else if (division > 32) 227 division = 32; 228 229 _regs[Clock_control] = (_regs[Clock_control] & ~(0x1f << Clock_lcd_divider)) | 230 ((division - 1) << Clock_lcd_divider); 231 } 232 233 // LCD pixel clock divider. 234 235 uint16_t 236 Cpm_jz4740_chip::get_lcd_pixel_divider() 237 { 238 return (_regs[Lcd_divider] >> Lcd_divider_value) + 1; 239 } 240 241 void 242 Cpm_jz4740_chip::set_lcd_pixel_divider(uint16_t division) 243 { 244 if (division == 0) 245 division = 1; 246 else if (division > 2048) 247 division = 2048; 248 249 _regs[Lcd_divider] = (_regs[Lcd_divider] & ~(0x7ff << Lcd_divider_value)) | 250 ((division - 1) << Lcd_divider_value); 251 } 252 253 254 255 // Set the device and pixel frequencies, indicating the latter and 256 // providing the device:pixel frequency ratio. 257 258 uint32_t 259 Cpm_jz4740_chip::get_frequency(enum Clock_frequency_identifiers clock) 260 { 261 if (clock == Clock_frequency_lcd_pixel) 262 return get_output_frequency() / get_lcd_pixel_divider(); 263 264 // NOTE: Consider a better error result. 265 return 0; 266 } 267 268 void 269 Cpm_jz4740_chip::set_frequency(enum Clock_frequency_identifiers clock, uint32_t frequency) 270 { 271 uint32_t out = get_output_frequency(); 272 273 switch (clock) 274 { 275 // Limit the device frequency to 150MHz. 276 277 case Clock_frequency_lcd: 278 if (frequency > 150000000) 279 frequency = 150000000; 280 set_lcd_device_divider(out / frequency); 281 break; 282 283 case Clock_frequency_lcd_pixel: 284 set_lcd_pixel_divider(out / frequency); 285 break; 286 287 default: 288 break; 289 } 290 } 291 292 293 294 uint32_t 295 Cpm_jz4740_chip::get_pll_frequency() 296 { 297 // Test for PLL enable and not PLL bypass. 298 299 if (pll_enabled() && !pll_bypassed()) 300 return (_exclk_freq * get_multiplier()) / 301 (get_input_division() * get_output_division()); 302 else 303 return _exclk_freq; 304 } 305 306 // Clock frequency for MSC, I2S, LCD and USB. 307 308 uint32_t 309 Cpm_jz4740_chip::get_output_frequency() 310 { 311 return get_pll_frequency() / get_source_divider(); 312 } 313 314 void 315 Cpm_jz4740_chip::update_output_frequency() 316 { 317 _regs[Clock_control] = _regs[Clock_control] | (1 << Clock_enable); 318 } 319 320 // Clock frequency for the CPU. 321 322 uint32_t Cpm_jz4740_chip::get_cpu_frequency() 323 { 324 return get_pll_frequency() / get_cpu_divider(); 325 } 326 327 // Clock frequency for fast peripherals. 328 329 uint32_t 330 Cpm_jz4740_chip::get_hclock_frequency() 331 { 332 return get_pll_frequency() / get_hclock_divider(); 333 } 334 335 // Clock frequency for slow peripherals. 336 337 uint32_t 338 Cpm_jz4740_chip::get_pclock_frequency() 339 { 340 return get_pll_frequency() / get_pclock_divider(); 341 } 342 343 // Clock frequency for the memory. 344 345 uint32_t 346 Cpm_jz4740_chip::get_memory_frequency() 347 { 348 return get_pll_frequency() / get_memory_divider(); 349 } 350 351 352 353 // C language interface functions. 354 355 void 356 *jz4740_cpm_init(l4_addr_t cpm_base) 357 { 358 /* Initialise the clock and power management peripheral with the 359 register memory region and a 12MHz EXCLK frequency. */ 360 361 return (void *) new Cpm_jz4740_chip(cpm_base, 12000000); 362 } 363 364 int 365 jz4740_cpm_have_clock(void *cpm, enum Clock_identifiers clock) 366 { 367 return static_cast<Cpm_jz4740_chip *>(cpm)->have_clock(clock); 368 } 369 370 void 371 jz4740_cpm_start_clock(void *cpm, enum Clock_identifiers clock) 372 { 373 static_cast<Cpm_jz4740_chip *>(cpm)->start_clock(clock); 374 } 375 376 void 377 jz4740_cpm_stop_clock(void *cpm, enum Clock_identifiers clock) 378 { 379 static_cast<Cpm_jz4740_chip *>(cpm)->stop_clock(clock); 380 } 381 382 uint32_t 383 jz4740_cpm_get_cpu_frequency(void *cpm) 384 { 385 return static_cast<Cpm_jz4740_chip *>(cpm)->get_cpu_frequency(); 386 } 387 388 uint32_t 389 jz4740_cpm_get_hclock_frequency(void *cpm) 390 { 391 return static_cast<Cpm_jz4740_chip *>(cpm)->get_hclock_frequency(); 392 } 393 394 uint32_t 395 jz4740_cpm_get_output_frequency(void *cpm) 396 { 397 return static_cast<Cpm_jz4740_chip *>(cpm)->get_output_frequency(); 398 } 399 400 uint32_t 401 jz4740_cpm_get_pclock_frequency(void *cpm) 402 { 403 return static_cast<Cpm_jz4740_chip *>(cpm)->get_pclock_frequency(); 404 } 405 406 uint32_t 407 jz4740_cpm_get_memory_frequency(void *cpm) 408 { 409 return static_cast<Cpm_jz4740_chip *>(cpm)->get_memory_frequency(); 410 } 411 412 uint16_t 413 jz4740_cpm_get_lcd_pixel_divider(void *cpm) 414 { 415 return static_cast<Cpm_jz4740_chip *>(cpm)->get_lcd_pixel_divider(); 416 } 417 418 uint32_t 419 jz4740_cpm_get_frequency(void *cpm, enum Clock_frequency_identifiers clock) 420 { 421 return static_cast<Cpm_jz4740_chip *>(cpm)->get_frequency(clock); 422 } 423 424 void 425 jz4740_cpm_set_frequency(void *cpm, enum Clock_frequency_identifiers clock, uint32_t frequency) 426 { 427 static_cast<Cpm_jz4740_chip *>(cpm)->set_frequency(clock, frequency); 428 } 429 430 void 431 jz4740_cpm_update_output_frequency(void *cpm) 432 { 433 static_cast<Cpm_jz4740_chip *>(cpm)->update_output_frequency(); 434 }