ADSP SoC Clock Architecture

The ADSP clock architecture is made up of a few core hardware blocks that are responsible for generating and routing core system clocks throughout the SoC. The exact clock trees between SoCs can vary however they share a common structure between them, allowing customers to configure system clocks to their specific needs.

Clock Generation Unit (CGU)

The CGU is responsible for generating all core system clocks used throughout the ADSP SoCs, it is made up of various core parts such as the PLL (phased-locked loops), divider clocks and clock gating control. The ADSP SoCs have two CGU blocks, CGU0 and CGU1. By default core system peripherals are clocked via CGU0 (the default CGU) and CGU1 can be used to further configure and customise how peripherals are clocked across the SoC.

The following diagram shows the high level clock generation path used by ADSP SoCs. Both CGUs are fed by a SYS_CLKINx reference clock, which operate at a fixed frequency of 25 MHz. Each CGU PLL uses the reference clock to generate a PLL clock output (PLLCLK), which is then routed through upstream dividers to produce the core system clocks required by the SoC.

        flowchart LR

    subgraph ROOT["Root Oscillator Clocks"]
        direction TB
        CLKIN0["SYS_CLKIN0"]
        CLKIN1["SYS_CLKIN1"]
    end

    CLKINSEL["CLKINSEL"]

    subgraph CLOCKGEN["Clock Generation Units"]
        direction LR
        CGU0["CGU0<br/>PLL + dividers"]
        CGU1["CGU1<br/>PLL + dividers"]
    end

    CDU["Clock Distribution Unit"]

    ARM["ARM core"]
    SHARC["SHARC cores"]
    DDR["DDR"]
    PERIPH["Peripherals"]

    CLKIN0 --> CGU0

    CLKIN0 --> CLKINSEL
    CLKIN1 --> CLKINSEL
    CLKINSEL --> CGU1

    CGU0 -->|"Generated clocks"| CDU
    CGU1 -->|"Generated clocks"| CDU

    CDU --> ARM
    CDU --> SHARC
    CDU --> DDR
    CDU --> PERIPH
    

Note

  • The CLKINSEL mux selects either SYS_CLKIN0 or SYS_CLKIN1 as the reference clock for CGU1.

  • The ADSP-SC846 SoC does not have a CLKINSEL mux. Both CGUs use SYS_CLKIN0 by default.

  • The ADSP-SC598 has a specialised PLL block which clocks the DDR memory controller. This allows DDR to be clocked via the CDU or via the PLL DCLK clock.

Each CGU can be configured independently, allowing different groups of system clocks to operate at different frequencies. The divider stage determines the frequency of each generated clock output, while clock gating controls whether individual clocks are enabled or disabled.

The CGU is responsible for generating these clock sources. Selection and routing of the generated clocks to core peripherals such as processors, memory controllers, ethernet etc. is handled separately by the Clock Distribution Unit (CDU).

Clock Distribution Unit (CDU)

The Clock Distribution Unit (CDU) selects and routes clocks generated by the CGUs to core SoC peripherals. Each CDU output (CLKO[n]) contains a multiplexer that selects one of up to four possible input clocks, along with clock gating functionality used to enable or disable the output. Each CLKO[n] is connected to a fixed SoC target and has a predefined set of possible parent/input clocks.

The number of configurable CDU outputs varies between ADSP SoCs. The ADSP-SC573, ADSP-SC589, ADSP-SC594, ADSP-SC598 and ADSP-SC846 provide 8, 10, 12, 14 and 14 configurable CDU outputs respectively.

        flowchart LR

    subgraph CGU["CGU0 and CGU1 clocks"]

        CLK0["Clock source 0"]
        CLK1["Clock source 1"]
        CLK2["Clock source 2"]
        CLK3["Clock source 3"]
    end

    subgraph CDU["Clock Distribution Unit (CDU)"]
        MUX{"Input Clock Select<br/>(SEL)"}
        GATE["Clock Enable Output<br/>(EN)"]

        MUX --> GATE
    end

    TARGET["Fixed SoC target<br/> peripheral"]

    CLK0 --> MUX
    CLK1 --> MUX
    CLK2 --> MUX
    CLK3 --> MUX

    GATE --> TARGET
    

Note

Some CDU clock selections are not available for user configuration. Inputs marked N/A do not have a valid clock source and must not be selected. Clocks or outputs marked Reserved are reserved for internal processor use and must not be configured by software.

Initial U-Boot clock configuration

The initial ADSP clock configuration is programmed by U-Boot during early system initialisation before Linux has started. U-Boot configures the CDU followed by the CGU during this phase. The CDU initialisation selects the reference clock used by CGU1 and programs the initial input clock selection and enable state for each supported CLKO[n] CDU output.

The CGU initialisation configures CGU0 and CGU1 using the SoC specific clock settings, including the PLL multiplier, input divider, and system clock divisors, etc. The PLL is placed into bypass mode while the new configuration is programmed and is then returned to normal operation once the clock settings have been applied.

After Linux starts, the ADSP clock drivers register the CGU and CDU clocks with the Linux common clock framework.

Peripheral drivers can then obtain and manage their required clocks through the Linux common clock framework APIs. Drivers can request operations such as enabling or disabling a clock, changing its rate, or selecting a parent clock where supported. These requests are handled by the corresponding ADSP clock provider, which performs the required hardware configuration. For CDU output clocks for example, these requests are translated by the CDU clock driver into the corresponding clock mux and gate operations.

ADSP Clock Drivers

The ADSP Linux clock implementation consists of shared clock drivers and SoC specific drivers:

Driver

Description

clk-adi-pll.c

Shared CGU PLL clock implementation

clk-adsp-cdu.c

Shared Clock Distribution Unit driver

clk-adi-sc57x.c

Clock Generation Unit driver for the ADSP-SC573 SoC

clk-adi-sc58x.c

Clock Generation Unit driver for the ADSP-SC589 SoC

clk-adi-sc594.c

Clock Generation Unit driver for the ADSP-SC594 SoC

clk-adi-sc598.c

Clock Generation Unit driver for the ADSP-SC598 SoC

clk-adi-sc846.c

Clock Generation Unit driver for the ADSP-SC846 SoC

To find the latest ADSP clock drivers navigate to drivers/clk/adi in the ADI Linux kernel repository. See Linux support for the current ADSP Linux development and release branches.

Note

ADSP Linux clock support is under active development, and feature coverage may vary between SoCs.

Debugging ADSP clocks

Linux provides several debugfs interfaces that can be used to inspect the ADSP clock configuration. The Linux common clock framework provides a summary of the Linux clock topology, while the ADSP CDU driver provides access to the current CDU hardware register state.

CCF clock summary

The Linux common clock framework exposes clk_summary through debugfs. This provides an overview of the clocks registered with Linux, including their parent relationships, enable states, and current clock rates.

To view the clock summary:

$ cat /sys/kernel/debug/clk/clk_summary

Example output:

cgu0_cdiv         1       1        0        1000000000  0          0     50000      Y                  deviceless                      no_connection_id
  cclk0_0         1       1        0        1000000000  0          0     50000      Y                    deviceless                     no_connection_id
    cdu_sharc0    1       1        0        1000000000  0          0     50000      Y                       deviceless                    no_connection_id

The example above shows cdu_sharc0 derived from its parent clock cclk0_0 which is in turn derived from the divider clock cgu0_cdiv. The indentation reflects the parent child relationship between the clocks.

The output can be used to verify the clock hierarchy and confirm the rate and enable state of clocks managed by the ADSP clock drivers.

CDU register dump

The ADSP CDU driver exposes a register dump through debugfs for CDU specific clock debugging. The dump provides the live CDU register state, including input clock selection, output enable state, and CDU status information.

To view the CDU register dump:

$ cat /sys/kernel/debug/clk/cdu_sharc0/cdu_cfg[0]

Example output:

CDU_CFG[0]: 0x00000001
  EN:   1
  SEL:  0
  LOCK: 0
CDU_STAT: 0x00000000
  LWERR:  0
  ADRERR: 0
  CLKO0:  0

The example above shows the CLKO0 CDU output enabled with input clock IN0_CLKO0 selected. The CDU output is not locked, and CLKO0 shows that the CDU output is not currently undergoing a configuration change. LWERR and ADRERR are both clear, indicating that no lock write or invalid address errors have been reported.

The clk_summary output shows the clock topology as registered with the Linux common clock framework, while the CDU register dump provides the corresponding hardware configuration state. Using both interfaces can help identify differences between the clock configuration expected by Linux and the current CDU hardware state.

ADSP Clock Trees

The clock tree structure varies between ADSP SoCs depending on the available CGU, CDU, and peripheral clocking requirements. The following section details SoC-specific clock trees and how clocks are routed throughout the system.

SC598 Clock Tree

        graph LR
    Crystal["SYS_CLKIN0<br/>25 MHz<br/>(sc5xx.dtsi:80)"]

    subgraph CGU0["CGU0 - Main CGU (clk-adi-sc598.c:100-209)"]
        DF0["DF: ÷1<br/>25 MHz"]
        VCO0["VCO: ×80<br/>2000 MHz<br/>(CGU_CTL MSEL)"]
        PLL0["PLLCLK: ÷2<br/>1000 MHz<br/>(line 125-128)"]

        CCLK0_0["CCLK0_0<br/>500 MHz<br/>÷2 (CDIV)"]
        SYSCLK0["SYSCLK_0<br/>250 MHz<br/>÷4 (SYSSEL)"]
        DCLK0["DCLK_0<br/>333 MHz<br/>÷3 (DDIV)"]
        OCLK0["OCLK_0<br/>62.5 MHz<br/>÷16 (ODIV)"]

        SCLK0_0["SCLK0_0<br/>62.5 MHz<br/>÷4"]
        SCLK1_0["SCLK1_0<br/>125 MHz<br/>÷2"]

        CCLK2_0["CCLK2_0<br/>667 MHz<br/>VCO÷3 direct<br/>(line 158-161)"]

        DCLK0_HALF["DCLK_0_HALF<br/>167 MHz<br/>÷2 (line 223)"]
    end

    subgraph CGU1["CGU1 - Secondary CGU (clk-adi-sc598.c:104-220)"]
        DF1["DF: ÷1<br/>25 MHz"]
        VCO1["VCO: ×64<br/>1600 MHz"]
        PLL1["PLLCLK: ÷2<br/>800 MHz"]

        CCLK0_1["CCLK0_1<br/>400 MHz<br/>÷2"]
        SYSCLK1["SYSCLK_1<br/>200 MHz<br/>÷4"]
        DCLK1["DCLK_1<br/>400 MHz<br/>÷2"]
        OCLK1["OCLK_1<br/>50 MHz<br/>÷16"]

        SCLK0_1["SCLK0_1<br/>50 MHz<br/>÷4"]
        SCLK1_1["SCLK1_1<br/>100 MHz<br/>÷2"]

        CCLK2_1["CCLK2_1<br/>533 MHz<br/>VCO÷3 direct"]

        SCLK1_1_HALF["SCLK1_1_HALF<br/>50 MHz<br/>÷2 (line 231)"]
    end

    subgraph PLL3["3rd PLL - Optional DDR Source<br/>(clk-adi-sc598.c:108-111, clkinit.c:383-440)"]
        PLL3_VCO["VCO: ×64<br/>1600 MHz"]
        PLL3_OUT["3PLL_DDIV<br/>400 MHz<br/>÷2"]
        PLL3_DDR_SEL["DDR Mux<br/>(line 285-287)<br/>selects DCLK_0 or 3PLL"]
    end

    subgraph CDU["CDU - Clock Distribution (0x3108F000)"]
        SHARC0["CLKO0: SHARC-FX<br/>500 MHz<br/>(CDU_CFG0)"]
        SHARC1["CLKO1: SHARC1<br/>500 MHz<br/>(CDU_CFG1)"]
        ARM["CLKO2: ARM<br/>533 MHz<br/>(CDU_CFG2)"]
        DDR["CLKO3: DDR<br/>333 MHz<br/>(CDU_CFG3)"]
        CAN["CLKO4: CAN<br/>50 MHz<br/>(CDU_CFG4)"]
        SPDIF["CLKO5: SPDIF<br/>125 MHz<br/>(CDU_CFG5)"]
        SPI["CLKO6: SPI<br/>62.5 MHz<br/>(CDU_CFG6)"]
        GIGE["CLKO7: GigE<br/>62.5 MHz<br/>(CDU_CFG7)"]
        LP["CLKO8: LP<br/>400 MHz<br/>(CDU_CFG8)"]
        LP_DDR["CLKO9: LP_DDR<br/>62.5 MHz<br/>(CDU_CFG9)"]
        OSPI["CLKO10: OSPI<br/>100 MHz<br/>(CDU_CFG10)"]
        TRACE["CLKO12: TRACE<br/>62.5 MHz<br/>(CDU_CFG12)"]
        EMMC["CLKO13: EMMC<br/>167 MHz<br/>(CDU_CFG13)"]
        EMMC_TMR["CLKO14: EMMC_TMR<br/>50 MHz<br/>(CDU_CFG14)"]
    end

    Crystal --> DF0
    Crystal --> DF1
    Crystal --> PLL3_VCO

    DF0 --> VCO0
    VCO0 --> PLL0
    VCO0 --> CCLK2_0

    PLL0 --> CCLK0_0
    PLL0 --> SYSCLK0
    PLL0 --> DCLK0
    PLL0 --> OCLK0

    SYSCLK0 --> SCLK0_0
    SYSCLK0 --> SCLK1_0
    DCLK0 --> DCLK0_HALF

    DF1 --> VCO1
    VCO1 --> PLL1
    VCO1 --> CCLK2_1

    PLL1 --> CCLK0_1
    PLL1 --> SYSCLK1
    PLL1 --> DCLK1
    PLL1 --> OCLK1

    SYSCLK1 --> SCLK0_1
    SYSCLK1 --> SCLK1_1
    SCLK1_1 --> SCLK1_1_HALF

    PLL3_VCO --> PLL3_OUT
    PLL3_OUT --> PLL3_DDR_SEL
    DCLK0 --> PLL3_DDR_SEL

    CCLK0_0 -.-> SHARC0
    CCLK0_0 -.-> SHARC1
    CCLK2_1 -.-> ARM
    PLL3_DDR_SEL -.-> DDR
    OCLK1 -.-> CAN
    SCLK1_0 -.-> SPDIF
    SCLK0_0 -.-> SPI
    SCLK0_0 -.-> GIGE
    CCLK0_1 -.-> LP
    OCLK0 -.-> LP_DDR
    SCLK1_1 -.-> OSPI
    SCLK0_0 -.-> TRACE
    DCLK0_HALF -.-> EMMC
    SCLK1_1_HALF -.-> EMMC_TMR

    classDef cgu0 fill:#e1f5ff,stroke:#01579b,stroke-width:2px
    classDef cgu1 fill:#fff3e0,stroke:#e65100,stroke-width:2px
    classDef pll3 fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
    classDef cdu fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px

    class CCLK0_0,SYSCLK0,DCLK0,OCLK0,SCLK0_0,SCLK1_0,CCLK2_0,DCLK0_HALF cgu0
    class CCLK0_1,SYSCLK1,DCLK1,OCLK1,SCLK0_1,SCLK1_1,CCLK2_1,SCLK1_1_HALF cgu1
    class PLL3_OUT pll3
    class SHARC0,SHARC1,ARM,DDR,CAN,SPDIF,SPI,GIGE,LP,LP_DDR,OSPI,TRACE,EMMC,EMMC_TMR cdu
    
  • Solid lines: Clock flow through dividers

  • Dashed lines: CDU mux selection to peripherals

  • Blue boxes: CGU0 outputs (Main CGU)

  • Orange boxes: CGU1 outputs (Secondary CGU)

  • Purple boxes: 3rd PLL outputs (DDR-specific)

  • Green boxes: CDU peripheral outputs