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
CLKINSELmux selects eitherSYS_CLKIN0orSYS_CLKIN1as the reference clock for CGU1.The ADSP-SC846 SoC does not have a
CLKINSELmux. Both CGUs useSYS_CLKIN0by 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
DCLKclock.
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 |
|---|---|
|
Shared CGU PLL clock implementation |
|
Shared Clock Distribution Unit driver |
|
Clock Generation Unit driver for the ADSP-SC573 SoC |
|
Clock Generation Unit driver for the ADSP-SC589 SoC |
|
Clock Generation Unit driver for the ADSP-SC594 SoC |
|
Clock Generation Unit driver for the ADSP-SC598 SoC |
|
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