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, 2023 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_change_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] | (1 << Clock_change_enable); 230 231 _regs[Clock_control] = (_regs[Clock_control] & ~(0x1f << Clock_lcd_divider)) | 232 ((division - 1) << Clock_lcd_divider); 233 234 _regs[Clock_control] = _regs[Clock_control] & ~(1 << Clock_change_enable); 235 } 236 237 // LCD pixel clock divider. 238 239 uint16_t 240 Cpm_jz4740_chip::get_lcd_pixel_divider() 241 { 242 return (_regs[Lcd_divider] >> Lcd_divider_value) + 1; 243 } 244 245 void 246 Cpm_jz4740_chip::set_lcd_pixel_divider(uint16_t division) 247 { 248 if (division == 0) 249 division = 1; 250 else if (division > 2048) 251 division = 2048; 252 253 _regs[Clock_control] = _regs[Clock_control] | (1 << Clock_change_enable); 254 255 _regs[Lcd_divider] = (_regs[Lcd_divider] & ~(0x7ff << Lcd_divider_value)) | 256 ((division - 1) << Lcd_divider_value); 257 258 _regs[Clock_control] = _regs[Clock_control] & ~(1 << Clock_change_enable); 259 } 260 261 262 263 uint32_t 264 Cpm_jz4740_chip::get_frequency(enum Clock_identifiers clock) 265 { 266 if (clock == Clock_lcd_pixel) 267 return get_source_frequency() / get_lcd_pixel_divider(); 268 269 // NOTE: Consider a better error result. 270 return 0; 271 } 272 273 void 274 Cpm_jz4740_chip::set_frequency(enum Clock_identifiers clock, uint32_t frequency) 275 { 276 uint32_t out = get_source_frequency(); 277 278 switch (clock) 279 { 280 // Limit the device frequency to 150MHz. 281 282 case Clock_lcd: 283 if (frequency > 150000000) 284 frequency = 150000000; 285 set_lcd_device_divider(out / frequency); 286 break; 287 288 case Clock_lcd_pixel: 289 set_lcd_pixel_divider(out / frequency); 290 break; 291 292 default: 293 break; 294 } 295 } 296 297 298 299 uint32_t 300 Cpm_jz4740_chip::get_pll_frequency() 301 { 302 // Test for PLL enable and not PLL bypass. 303 304 if (pll_enabled() && !pll_bypassed()) 305 return (_exclk_freq * get_multiplier()) / 306 (get_input_division() * get_output_division()); 307 else 308 return _exclk_freq; 309 } 310 311 // Clock frequency for MSC, I2S, LCD and USB. 312 313 uint32_t 314 Cpm_jz4740_chip::get_source_frequency() 315 { 316 return get_pll_frequency() / get_source_divider(); 317 } 318 319 // Clock frequency for the CPU. 320 321 uint32_t Cpm_jz4740_chip::get_cpu_frequency() 322 { 323 return get_pll_frequency() / get_cpu_divider(); 324 } 325 326 // Clock frequency for fast peripherals. 327 328 uint32_t 329 Cpm_jz4740_chip::get_hclock_frequency() 330 { 331 return get_pll_frequency() / get_hclock_divider(); 332 } 333 334 // Clock frequency for slow peripherals. 335 336 uint32_t 337 Cpm_jz4740_chip::get_pclock_frequency() 338 { 339 return get_pll_frequency() / get_pclock_divider(); 340 } 341 342 // Clock frequency for the memory. 343 344 uint32_t 345 Cpm_jz4740_chip::get_memory_frequency() 346 { 347 return get_pll_frequency() / get_memory_divider(); 348 } 349 350 351 352 // C language interface functions. 353 354 void 355 *jz4740_cpm_init(l4_addr_t cpm_base) 356 { 357 /* Initialise the clock and power management peripheral with the 358 register memory region and a 12MHz EXCLK frequency. */ 359 360 return (void *) new Cpm_jz4740_chip(cpm_base, 12000000); 361 } 362 363 int 364 jz4740_cpm_have_clock(void *cpm, enum Clock_identifiers clock) 365 { 366 return static_cast<Cpm_jz4740_chip *>(cpm)->have_clock(clock); 367 } 368 369 void 370 jz4740_cpm_start_clock(void *cpm, enum Clock_identifiers clock) 371 { 372 static_cast<Cpm_jz4740_chip *>(cpm)->start_clock(clock); 373 } 374 375 void 376 jz4740_cpm_stop_clock(void *cpm, enum Clock_identifiers clock) 377 { 378 static_cast<Cpm_jz4740_chip *>(cpm)->stop_clock(clock); 379 } 380 381 uint32_t 382 jz4740_cpm_get_cpu_frequency(void *cpm) 383 { 384 return static_cast<Cpm_jz4740_chip *>(cpm)->get_cpu_frequency(); 385 } 386 387 uint32_t 388 jz4740_cpm_get_hclock_frequency(void *cpm) 389 { 390 return static_cast<Cpm_jz4740_chip *>(cpm)->get_hclock_frequency(); 391 } 392 393 uint32_t 394 jz4740_cpm_get_source_frequency(void *cpm) 395 { 396 return static_cast<Cpm_jz4740_chip *>(cpm)->get_source_frequency(); 397 } 398 399 uint32_t 400 jz4740_cpm_get_pclock_frequency(void *cpm) 401 { 402 return static_cast<Cpm_jz4740_chip *>(cpm)->get_pclock_frequency(); 403 } 404 405 uint32_t 406 jz4740_cpm_get_memory_frequency(void *cpm) 407 { 408 return static_cast<Cpm_jz4740_chip *>(cpm)->get_memory_frequency(); 409 } 410 411 uint16_t 412 jz4740_cpm_get_lcd_pixel_divider(void *cpm) 413 { 414 return static_cast<Cpm_jz4740_chip *>(cpm)->get_lcd_pixel_divider(); 415 } 416 417 uint32_t 418 jz4740_cpm_get_frequency(void *cpm, enum Clock_identifiers clock) 419 { 420 return static_cast<Cpm_jz4740_chip *>(cpm)->get_frequency(clock); 421 } 422 423 void 424 jz4740_cpm_set_frequency(void *cpm, enum Clock_identifiers clock, uint32_t frequency) 425 { 426 static_cast<Cpm_jz4740_chip *>(cpm)->set_frequency(clock, frequency); 427 }