nxp,imx-ccm-rev3
Description
NXP i.MX Clock Controller Module, rev3.
The i.MX CCM models a peripheral's clock as two independent things: a gate
(LPCG) that turns its bus and functional clocks on and off, and a clock root
whose mux and dividers determine the frequency it receives. The two identifier
spaces are unrelated, and both have to reach this driver.
The shared NXP peripheral drivers read one clock cell -- DT_INST_CLOCKS_CELL(n,
name) -- and pass that single value to clock_control_on(),
clock_control_get_rate(), and clock_control_configure() alike, so a second cell
would be invisible to them. Both identifiers are therefore packed into the one
cell with IMX_CCM_CLK():
#include <nxp/imxrt/imxrt266x/clock/imx_ccm_rev3_rt266x.h>
lpuart0: serial@42190000 {
compatible = "nxp,lpuart";
clocks = <&ccm IMX_CCM_CLK(IMX_CCM_LPCG_MAIN_HSP_LPUART0,
IMX_CCM_ROOT_MAIN_LPUART0_FCLK)>;
};
Use IMX_CCM_GATE_NONE for a peripheral with no gate of its own, and
IMX_CCM_ROOT_NONE for one with no dedicated root; asking the latter for its
rate returns -ENOTSUP rather than a fabricated number.
Configurable clock roots are child nodes with compatible
"nxp,imx-ccm-rev3-root". The controller programs every enabled root child at
initialization, in devicetree order, so a board or application overlay can
retarget an individual root without touching C:
&peri3_rootclk {
clock-mux = <IMX_CCM_MUX_PERI3_SYSPLL_DIVOUT2>;
};
Per-device clock roots (a "source" clocks entry)
-----------------------------------------------
A root child node states how a root is configured, but it says nothing about
which peripheral cares, so setting a peripheral's frequency means editing two
unrelated places: the peripheral node for its gate and rate lookup, and a ccm
child for its mux and dividers. A peripheral that owns its root exclusively
can instead carry that root's configuration itself, in its `clocks` property,
as a second entry beyond the gate:
lpuart0: serial@42190000 {
compatible = "nxp,lpuart";
clock-names = "gate", "source";
clocks = <&ccm IMX_CCM_CLK(IMX_CCM_LPCG_MAIN_HSP_LPUART0,
IMX_CCM_ROOT_MAIN_LPUART0_FCLK)>,
<&ccm IMX_CCM_ROOT_CFG(IMX_CCM_ROOT_MAIN_LPUART0_FCLK,
IMX_CCM_MUX_LPUART0_PERI3, 5, 1)>;
};
There are thus two KINDS of single-cell clock specifier, both #clock-cells 1:
- IMX_CCM_CLK(gate, root) -- the usual gate + rate-lookup cell, read by
clock_control_on()/_get_rate(). Named "gate" on the nodes here.
- IMX_CCM_ROOT_CFG(root, mux, div, snd_div) -- a root-configuration cell,
naming the root to program, its mux source, and its two dividers (the same
values a "nxp,imx-ccm-rev3-root" child would carry). Neither divider may
be 0. Named "source", always.
The two kinds are distinguished by a fixed tag in the cell's top nibble that a
gate cell can never produce, so the controller can tell them apart: the shared
LPUART and LPSPI drivers already hand it the gate cell on
clock_control_configure() unconditionally, and it must ignore that rather than
misread it as a root.
clock-names is what selects the root-configuration cell: the consuming driver
looks it up by the name "source" and passes the cell to
clock_control_configure() before it enables the peripheral's gate, so the root
is programmed when the device is actually brought up rather than for every
root at controller init. A node that names no "source" entry -- every NXP
family without clock roots -- gets the previous behaviour, since the same
driver serves both.
"source" is the only name this binding reserves, and it is deliberately the
only one. clock-names is a vocabulary each consuming driver owns: FlexCAN and
WDOG32 name their entries "clksrc0"/"clksrc1" and pick one with clk-source,
the system counter names them "base"/"slow", MIPI-DSI "dphy"/"esc"/"pixel".
Forcing a single name on entry 0 would collide with those. "gate" is what a
node uses when its driver has no opinion, as the LPUART and LPSPI nodes here
do; a peripheral whose driver does name its entries keeps those names and
appends "source". Looking the entry up by name rather than by index is what
makes that appending safe.
"source" and FlexCAN's "clksrc0"/"clksrc1" both read as picking a clock source,
but they are different muxes at different levels and do not substitute for each
other. A "clksrcN" entry is a whole gate cell naming a DIFFERENT upstream clock,
and clk-source both selects which one the driver uses and is written to the CAN
engine's own 1-bit mux inside the IP. A "source" entry names no new clock: it
carries the mux and dividers of the SAME root the "gate" entry already points
at, in the CCM outside the IP. Hence the different destinations -- a "clksrcN"
cell goes to clock_control_on()/_get_rate(), a "source" cell only ever to
clock_control_configure() -- and the different cardinality: clksrc0 and clksrc1
are alternatives, exactly one in use, while "source" is additive and there is at
most one.
Two restrictions follow, and both are on the peripheral rather than on this
controller:
- The consuming driver has to opt in -- look up "source" and call
clock_control_configure() with it. Until it does, the cell is inert
devicetree; put the root in a ccm child instead.
- Do not add a "source" entry to a node whose driver reads a clocks entry at
index 1 or beyond for its own purpose. The USB EHCI drivers (udc and uhc)
are the case in tree: they take clocks entry 1 as the USB PHY clock. There
the misread needs a second clock-rates element too, since that arm is
guarded on both, but the node would be wrong either way. Such a root stays
a ccm child. A misread that does happen is caught at boot rather than
silently -- a root-configuration cell handed to clock_control_on() fails
the gate range check with -EINVAL.
A peripheral node carries at most one root-configuration cell. One fed through
several roots keeps them as ccm children.
A root belongs either to a peripheral node or to a ccm child, never to both:
two writers of one root would race, and the last one would silently win.
Roots shared between peripherals stay ccm children.
One instance, or several
------------------------
Some SoCs implement the CCM as a single addressable block; others implement it as
several instances, one per subsystem. Both are declared the same way: one node per
hardware instance, each with its own "reg" and its own clock-root children. The
driver instantiates a device per node, so a single-block SoC needs no special case.
What makes that work is that the clock root and gate identifier spaces are flat
across the instances: the HAL selects the owning instance by testing an identifier
against per-instance ranges. A consumer's clocks phandle therefore names the clock
service rather than a particular block -- any instance resolves any specifier, and
the identifier in the cell already says which subsystem owns the resource. On an SoC
with several instances, point consumers at whichever instance is most natural and
keep it consistent.
A clock root is a child node whose "reg" is the register block that configures
it. A CCM numbers its slices per instance -- CLOCK_ROOT<N> in the reference
manual -- and each slice occupies a fixed, SoC-specific stride from the instance
base (0x10 on RT266x, 0x80 on RT1170, 0x40 on RT1180), so a root's reg is
<N * stride stride>:
cmpt_ccm: clock-controller@44060000 {
compatible = "nxp,imx-ccm-rev3";
reg = <0x44060000 0x4000>;
#clock-cells = <1>;
#address-cells = <1>;
#size-cells = <1>;
ranges = <0x0 0x44060000 0x4000>;
/* CLOCK_ROOT3 */
systick_rootclk: clock-root@30 {
compatible = "nxp,imx-ccm-rev3-root";
reg = <0x30 0x10>;
nxp,root-id = <IMX_CCM_ROOT_CMPT_SYSTICK_CLK0>;
clock-mux = <...>;
};
};
The HAL, by contrast, addresses a root by a flat identifier that spans every
instance. That identifier is not derived from reg: each root states it in its own
nxp,root-id property, so this binding needs no per-instance base and the driver
needs no arithmetic that a differently numbered SoC could invalidate. Declare a
root under the instance whose identifier range contains it. That placement is
descriptive: the HAL resolves the instance from the identifier rather than from
the node's position, so a root under the wrong instance is still programmed
correctly while describing the hardware wrongly. Initialization order across
instances follows the devicetree dependency ordinal rather than the order the
nodes are written, so two roots that need a fixed order relative to each other
must be children of the same instance.
Relationship to nxp,imx-ccm-rev2
--------------------------------
rev3 is a superset of rev2 rather than a parallel design, and this binding is
deliberately SoC-agnostic so it can absorb the rev2 users.
The rev2 parts use the same underlying model: RT1176 and RT1189 both gate
peripherals through clock_lpcg_t identifiers and configure roots through
CLOCK_SetRootClock(root, {clockOff, mux, div}). rev3 adds only a second
divider. rev2's binding declares three cells (name, offset, bits) but its
driver reads cell 0 only, so cells 1 and 2 carry no information.
Migrating an SoC from rev2 to rev3 therefore means:
1. replacing each peripheral's <name offset bits> with
IMX_CCM_CLK(gate, root),
2. expressing that SoC's soc.c CLOCK_SetRootClock() sequence as
nxp,imx-ccm-rev3-root child nodes, and
3. deleting its arm of the rev2 driver's per-peripheral switch.
Step 1 touches every board devicetree of the migrating family, which is why
no migration is attempted here.
Known exception: i.MX 93 and i.MX 95 resolve peripheral rates through
CLOCK_GetIpFreq() rather than CLOCK_GetRootClockFreq(). That is a different
resolver, so those SoCs need their own evaluation and may be better left on
rev2.
Properties
Properties not inherited from the base binding file.
Name |
Type |
Details |
|---|---|---|
|
|
Number of items to expect in a Clock specifier
This property is required. Constant value: |
Deprecated properties not inherited from the base binding file.
(None)
Properties inherited from the base binding file, which defines common properties that may be set on many nodes. Not all of these may apply to the “nxp,imx-ccm-rev3” compatible.
Name |
Type |
Details |
|---|---|---|
|
|
Information used to address the device. The value is specific to
the device (i.e. is different depending on the compatible
property).
The "reg" property is typically a sequence of (address, length) pairs.
Each pair is called a "register block". Values are
conventionally written in hex.
For details, see "2.3.6 reg" in Devicetree Specification v0.4.
This property is required. See Important properties for more information. |
|
|
This property encodes the number of <u32> cells used by address fields
in "reg" properties in this node's children.
For details, see "2.3.5 #address-cells and #size-cells" in Devicetree
Specification v0.4.
Constant value: |
|
|
This property encodes the number of <u32> cells used by size fields in
"reg" properties in this node's children.
For details, see "2.3.5 #address-cells and #size-cells" in Devicetree
Specification v0.4.
Constant value: |
|
|
Required on any instance that declares clock-root children, so their register
blocks translate to real addresses. An instance with no children may omit it.
|
|
|
Indicates the operational status of the hardware or other
resource that the node represents. In particular:
- "okay" means the resource is operational and, for example,
can be used by device drivers
- "disabled" means the resource is not operational and the system
should treat it as if it is not present
For details, see "2.3.4 status" in Devicetree Specification v0.4.
Legal values: See Important properties for more information. |
|
|
This property is a list of strings that essentially define what
type of hardware or other resource this devicetree node
represents. Each device driver checks for specific compatible
property values to find the devicetree nodes that represent
resources that the driver should manage.
The recommended format is "vendor,device", The "vendor" part is
an abbreviated name of the vendor. The "device" is usually from
the datasheet.
The compatible property can have multiple values, ordered from
most- to least-specific. Having additional values is useful when the
device is a specific instance of a more general family, to allow the
system to match the most specific driver available.
For details, see "2.3.1 compatible" in Devicetree Specification v0.4.
This property is required. See Important properties for more information. |
|
|
Optional names given to each register block in the "reg" property.
For example:
/ {
soc {
#address-cells = <1>;
#size-cells = <1>;
uart@1000 {
reg = <0x1000 0x2000>, <0x3000 0x4000>;
reg-names = "foo", "bar";
};
};
};
The uart@1000 node has two register blocks:
- one with base address 0x1000, size 0x2000, and name "foo"
- another with base address 0x3000, size 0x4000, and name "bar"
|
|
|
Information about interrupts generated by the device, encoded as an array
of one or more interrupt specifiers. The format of the data in this property
varies by where the device appears in the interrupt tree. Devices with the same
"interrupt-parent" will use the same format in their interrupts properties.
For details, see "2.4 Interrupts and Interrupt Mapping" in
Devicetree Specification v0.4.
See Important properties for more information. |
|
|
Extended interrupt specifier for device, used as an alternative to
the "interrupts" property.
For details, see "2.4 Interrupts and Interrupt Mapping" in
Devicetree Specification v0.4.
|
|
|
Optional names given to each interrupt generated by a device.
The interrupts themselves are defined in either "interrupts" or
"interrupts-extended" properties.
For details, see "2.4 Interrupts and Interrupt Mapping" in
Devicetree Specification v0.4.
|
|
|
If present, this refers to the node which handles interrupts generated
by this device.
For details, see "2.4 Interrupts and Interrupt Mapping" in
Devicetree Specification v0.4.
|
|
|
Human readable string describing the device. Use of this property is
deprecated except as needed on a case-by-case basis.
For details, see "4.1.2 Miscellaneous Properties" in Devicetree
Specification v0.4.
See Important properties for more information. |
|
|
Information about the device's clock providers. In general, this property
should follow conventions established in the dt-schema binding:
https://github.com/devicetree-org/dt-schema/blob/main/dtschema/schemas/clock/clock.yaml
|
|
|
Optional names given to each clock provider in the "clocks" property.
|
|
|
Indicates that the device is capable of coherent DMA operations.
For details, see "2.3.10 dma-coherent" in Devicetree Specification v0.4.
|
|
|
DMA channel specifiers relevant to the device.
|
|
|
Optional names given to the DMA channel specifiers in the "dmas" property.
|
|
|
The dma-ranges provides a means of defining a mapping or translation between the
physical address space of the bus and the physical address space of the parent of the bus.
For details, see "2.3.9 dma-ranges" in Devicetree Specification v0.4.
|
|
|
IO channel specifiers relevant to the device.
|
|
|
Optional names given to the IO channel specifiers in the "io-channels" property.
|
|
|
Mailbox / IPM channel specifiers relevant to the device.
|
|
|
Optional names given to the mbox specifiers in the "mboxes" property.
|
|
|
Power domain specifiers relevant to the device.
|
|
|
Optional names given to the power domain specifiers in the "power-domains" property.
|
|
|
Number of cells in power-domains property
|
|
|
HW spinlock id relevant to the device.
|
|
|
Optional names given to the hwlock specifiers in the "hwlocks" property.
|
|
|
Do not initialize device automatically on boot. Device should be manually
initialized using device_init().
|
|
|
Property to identify that a device can be used as wake up source.
When this property is provided a specific flag is set into the
device that tells the system that the device is capable of
wake up the system.
Wake up capable devices are disabled (interruptions will not wake up
the system) by default but they can be enabled at runtime if necessary.
|
|
|
Automatically configure the device for runtime power management after the
init function runs.
|
|
|
List of power states that will disable this device power.
|
Specifier cell names
clock cells: name