Landfall

pkg/devices/lib/cpm/src/jz4740.cc

160:2eafeaedb1f7
13 months ago Paul Boddie Attempt to simplify and improve various aspects of the CPM library, also adjusting various test programs. cpm-library-improvements
     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 }