* [PATCH v6 0/3] Cirrus Logic CS2600 clock device
@ 2026-06-30 15:55 Richard Fitzgerald
2026-06-30 15:55 ` [PATCH v6 1/3] dt-bindings: clock: cs2600: Add support for the CS2600 Richard Fitzgerald
` (2 more replies)
0 siblings, 3 replies; 10+ messages in thread
From: Richard Fitzgerald @ 2026-06-30 15:55 UTC (permalink / raw)
To: mturquette, sboyd, bmasney, robh, krzk+dt, conor+dt
Cc: linux-clk, devicetree, linux-kernel, patches
This patch set provides Common Clock Framework support for the
Cirrus Logic CS2600 Fractional-N Clock Synthesizer and Multiplier.
KUnit testing is included in the series.
Major changes:
v6 changes:
- Convert DT smart mode selection from an enum property to two booleans.
- Fix DT clock names to match the datasheet names.
- Add DT property to explicitly flag that the internal oscillator should
be used instead of inferring it from having a variable definition of
clock-names.
- Add DT support for setting BCLK invert, FSYNC invert, and FSYNC duty
cycles.
- More DT examples
- Merged and factored-out duplicated code in the driver.
- Fixed rounding errors that caused the calculated frequency to drift when
it was passed into clock core and then passed back to the driver.
- Fixed driver bugs where the functions for one clock relied on the
register values of other clocks being set already, which is not
guaranteed.
- Fix a bug where the (mandatory) REF_CLK_IN wasn't requested and enabled
when using CLK_IN as the frequency reference in manual mode.
- Lots of code cleanup in the driver
- Fix driver bugs.
- Added KUnit test.
v5 changes:
- Change dt-binding to dt-bindings in commit message
- Add minItems to clocks
- Remove unneeded comments for the cirrus,aux-output-source enums
v4 changes:
- Change DT discription to add more detial.
- Move reg property to keep consistent with the required list.
- Add enum to string DT inputs and change the input values to lower case.
- Change unevauatedProperties to additionalProperties
- Reove black line.
v3 changes:
- Changed clock-names in device tree to an emum since the both clocks
are not always required.
- Changed cirrus,aux-output-source to a string input.
- Added cirrus,clock-mode as a string input.
- Fixed ordering in DT example.
- Removed dt-bindings .h file.
- Add in_range to allow for acceptable ranges for clock rates.
v2 changes:
- Remove | from after "discription" in DT binding.
- Remove discription from "clocks" in DT binding.
- Change ref_clk to xti
- Clarify auxiliary clock pin
- Change DT example to not have "reg" for the bus
- Change DT example to change i2c@0 to i2c
- Remove "OUTPUT" from the clock ouputs in the dt binding include file.
- Remove AUX_OUT defines
- Use do_div when dividing u64 by a u32
- Include the dt bindings include file
- Use the includes from the dt bindings include file aux output.
- Move of_device_id and i2c_device_id tables next to i2c_driver struct.
Paul Handrigan (2):
dt-bindings: clock: cs2600: Add support for the CS2600
clk: cs2600: Add Fractional-N clock driver
Richard Fitzgerald (1):
clk: cs2600: Add KUnit test for CS2600 driver
.../bindings/clock/cirrus,cs2600.yaml | 262 ++
MAINTAINERS | 8 +
drivers/clk/Kconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/cirrus/Kconfig | 23 +
drivers/clk/cirrus/Makefile | 27 +
drivers/clk/cirrus/clk-cs2600-kunit.c | 2495 +++++++++++++++++
drivers/clk/cirrus/clk-cs2600-kunit.h | 18 +
drivers/clk/cirrus/clk-cs2600.c | 1274 +++++++++
drivers/clk/cirrus/clk-cs2600.h | 163 ++
drivers/clk/cirrus/kunit_clk_cs2600.dtsi | 40 +
.../clk/cirrus/kunit_clk_cs2600_assigned.dtso | 27 +
.../kunit_clk_cs2600_aux1_out_clk_in.dtso | 17 +
...it_clk_cs2600_aux1_out_clk_in_missing.dtso | 17 +
...kunit_clk_cs2600_aux1_out_freq_unlock.dtso | 17 +
...unit_clk_cs2600_aux1_out_phase_unlock.dtso | 17 +
.../kunit_clk_cs2600_aux1_out_ref_clk_in.dtso | 17 +
.../cirrus/kunit_clk_cs2600_bclk_invert.dtso | 17 +
.../cirrus/kunit_clk_cs2600_fsync_duty_1.dtso | 17 +
.../cirrus/kunit_clk_cs2600_fsync_duty_2.dtso | 17 +
.../kunit_clk_cs2600_fsync_duty_32.dtso | 17 +
.../cirrus/kunit_clk_cs2600_fsync_invert.dtso | 17 +
.../kunit_clk_cs2600_manual_intosc_clkin.dtso | 17 +
.../kunit_clk_cs2600_manual_intosc_only.dtso | 17 +
.../kunit_clk_cs2600_manual_refclk_clkin.dtso | 16 +
.../kunit_clk_cs2600_manual_refclk_only.dtso | 16 +
.../kunit_clk_cs2600_name_output_clocks.dtso | 23 +
...it_clk_cs2600_smart_clkin_only_intosc.dtso | 19 +
...it_clk_cs2600_smart_clkin_only_refclk.dtso | 18 +
.../cirrus/kunit_clk_cs2600_smart_intosc.dtso | 18 +
.../cirrus/kunit_clk_cs2600_smart_refclk.dtso | 17 +
.../dt-bindings/clock/cirrus,cs2600-clock.h | 18 +
32 files changed, 4688 insertions(+)
create mode 100644 Documentation/devicetree/bindings/clock/cirrus,cs2600.yaml
create mode 100644 drivers/clk/cirrus/Kconfig
create mode 100644 drivers/clk/cirrus/Makefile
create mode 100644 drivers/clk/cirrus/clk-cs2600-kunit.c
create mode 100644 drivers/clk/cirrus/clk-cs2600-kunit.h
create mode 100644 drivers/clk/cirrus/clk-cs2600.c
create mode 100644 drivers/clk/cirrus/clk-cs2600.h
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600.dtsi
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_assigned.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_clk_in.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_clk_in_missing.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_freq_unlock.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_phase_unlock.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_ref_clk_in.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_bclk_invert.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_fsync_duty_1.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_fsync_duty_2.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_fsync_duty_32.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_fsync_invert.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_manual_intosc_clkin.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_manual_intosc_only.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_manual_refclk_clkin.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_manual_refclk_only.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_name_output_clocks.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_smart_clkin_only_intosc.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_smart_clkin_only_refclk.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_smart_intosc.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_smart_refclk.dtso
create mode 100644 include/dt-bindings/clock/cirrus,cs2600-clock.h
--
2.47.3
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v6 1/3] dt-bindings: clock: cs2600: Add support for the CS2600
2026-06-30 15:55 [PATCH v6 0/3] Cirrus Logic CS2600 clock device Richard Fitzgerald
@ 2026-06-30 15:55 ` Richard Fitzgerald
2026-07-01 6:48 ` Krzysztof Kozlowski
2026-06-30 15:55 ` [PATCH v6 2/3] clk: cs2600: Add Fractional-N clock driver Richard Fitzgerald
2026-06-30 15:55 ` [PATCH v6 3/3] clk: cs2600: Add KUnit test for CS2600 driver Richard Fitzgerald
2 siblings, 1 reply; 10+ messages in thread
From: Richard Fitzgerald @ 2026-06-30 15:55 UTC (permalink / raw)
To: mturquette, sboyd, bmasney, robh, krzk+dt, conor+dt
Cc: linux-clk, devicetree, linux-kernel, patches
From: Paul Handrigan <paulha@opensource.cirrus.com>
Add device tree schema for the Cirrus Logic CS2600 clock generator.
The majority of the schema is typical clock, power and I2C
properties.
Passes dt_binding_check:
make dt_binding_check DT_SCHEMA_FILES=clock/cirrus,cs2600.yaml
SCHEMA Documentation/devicetree/bindings/processed-schema.json
CHKDT ./Documentation/devicetree/bindings
LINT ./Documentation/devicetree/bindings
STYLE ./Documentation/devicetree/bindings
DTEX Documentation/devicetree/bindings/clock/cirrus,cs2600.example.dts
DTC [C] Documentation/devicetree/bindings/clock/cirrus,cs2600.example.dtb
Signed-off-by: Paul Handrigan <paulha@opensource.cirrus.com>
Co-developed-by: Richard Fitzgerald <rf@opensource.cirrus.com>
Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
Changes in V6:
- Fixed clock naming in descriptions to match datasheet.
- Fixed clock-names property values to match datasheet names.
- Added cirrus,internal-oscillator boolean to flag that the internal
oscillator is the clock source. Previously the driver inferred this
if clock-names did not contain "ref_clk_in", but this made it difficult
to enforce dts correctness because there was no way to tell whether
ref_clk_in was intentionally or accidentally missing.
- Changed the cirrus,clock-mode enum property into two booleans, since
effectively it was two separate features masquerading as an enum:
- cirrus,smart-mode present to enable smart mode.
- cirrus,smart-mode-clkin-only to enable a feature where the output will
be suppressed until both input clocks are present.
- Changes to cirrus,aux-output-source property:
- Renamed to cirrus,aux1-output-source because it's for the AUX1 pin.
- Added more options.
- Renamed the "no_clkin" option to "clkin_missing".
- Reformatted the description as a list instead of one long sentence.
- Changed clock-names from an enum to an ordered list of const.
This implicitly ensures ref_clk_in is always required.
- Added properties to invert bclk and fsync outputs.
- Added property cirrus,fsync-duty-cycles.
- Added #clock-cells and vdd-supply to the list of required properies.
- Rewritten description description section.
- Reordered the property list to put common properties before custom
cirrus properties.
- Added more examples.
- Added header file to define the clock indexes for DT consumers of the
CS2600 clocks.
Note:
V5 was Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
but I haven't carried this forward because the changes in V6 are large.
Changes in V5:
- Change dt-binding to dt-bindings in commit message
- Add minItems to clocks
- Remove unneeded comments for the cirrus,aux-output-source enums
Changes in V4:
- Change DT description to add more detail.
- Move reg property to keep consistant with the required list.
- Add enum to string DT inputs and change the input values to lower case.
- Change unevaluatedProperties to additionalProperties
Changes in V3:
- Changed clock-names in device tree to an enum since the both clocks
are not always required.
- Changed cirrus,aux-output-source to a string input.
- Added cirrus,clock-mode as a string input.
- Fixed ordering in DT example.
- Removed dt-bindings .h file.
Changes in V2:
- Remove | from after "description" in DT binding.
- Remove description from "clocks" in DT binding.
- Change ref_clk to xti
- Clarify auxiliary clock pin
- Change DT example to not have "reg" for the bus
- Change DT example to change i2c@0 to i2c
- Remove "OUTPUT" from the clock ouputs in the dt binding include file.
.../bindings/clock/cirrus,cs2600.yaml | 262 ++++++++++++++++++
MAINTAINERS | 7 +
.../dt-bindings/clock/cirrus,cs2600-clock.h | 18 ++
3 files changed, 287 insertions(+)
create mode 100644 Documentation/devicetree/bindings/clock/cirrus,cs2600.yaml
create mode 100644 include/dt-bindings/clock/cirrus,cs2600-clock.h
diff --git a/Documentation/devicetree/bindings/clock/cirrus,cs2600.yaml b/Documentation/devicetree/bindings/clock/cirrus,cs2600.yaml
new file mode 100644
index 000000000000..5f7da2ba87a0
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/cirrus,cs2600.yaml
@@ -0,0 +1,262 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/cirrus,cs2600.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Cirrus Logic Fractional-N Clock Synthesizer & Clock Multiplier
+
+maintainers:
+ - patches@opensource.cirrus.com
+
+description: |
+ The CS2600 is a system-clocking device that enables frequency synthesis
+ and clock generation from a stable timing reference clock. The device can
+ generate low-jitter clocks from a noisy clock reference at frequencies as
+ low as 50 Hz. The CS2600 has two clock inputs: REF_CLK_IN and CLK_IN.
+ REF_CLK_IN can be either a crystal or an external clock source.
+ REF_CLK_IN is always required to clock the PLL. Either REF_CLK_IN or
+ CLK_IN can be used as the reference frequency from which the output
+ frequency is derived.
+
+ The clock tree of the CS2600 is:
+
+ +------------+
+ REF_CLK_IN ----| Hybrid PLL | PLL_OUT +--------+
+ | + FLL |-----+------| GATE |--------- CLK_OUT
+ CLK_IN ----| | | +--------+
+ +------------+ |
+ | +---------+
+ +------| DIVIDER |-------- BCLK_OUT
+ | +---------+
+ |
+ | +---------+
+ +------| DIVIDER |-------- FSYNC_OUT
+ +---------+
+
+ Clock output names are defined in <dt-bindings/clock/cirrus,cs2600-clock.h>
+
+properties:
+ compatible:
+ enum:
+ - cirrus,cs2600
+
+ reg:
+ maxItems: 1
+
+ '#clock-cells':
+ const: 1
+
+ clocks:
+ minItems: 1
+ maxItems: 2
+
+ clock-names:
+ minItems: 1
+ items:
+ - const: ref_clk_in
+ - const: clk_in
+
+ vdd-supply:
+ description: Power Supply
+
+ clock-output-names:
+ maxItems: 4
+ description:
+ Names for the output clocks corresponding to PLL_OUT, CLK_OUT,
+ BCLK_OUT and FSYNC_OUT.
+
+ cirrus,aux1-output-source:
+ description: |
+ Selects the signal routed to the AUX1_OUT pin:
+ - "ref_clk_in" the clock on REF_CLK_IN
+ - "clk_in" the clock on CLK_IN
+ - "phase_unlock" indicate the input and output clocks are not in phase,
+ - "freq_unlock" indicate the PLL is unlocked,
+ - "clkin_missing" indicate no clock present at clk_in pin.
+ $ref: /schemas/types.yaml#/definitions/string
+ enum:
+ - ref_clk_in
+ - clk_in
+ - phase_unlock
+ - freq_unlock
+ - clkin_missing
+ default: ref_clk_in
+
+ cirrus,internal-oscillator:
+ description:
+ Use the internal oscillator as the source of REF_CLK_IN. The clocks
+ and clock-names properties must provide a fixed-rate clock of exactly
+ 12000000 Hz as the ref_clk_in clock.
+ type: boolean
+
+ cirrus,smart-mode:
+ description:
+ Use REF_CLK_IN as the frequency reference until CLK_IN is present,
+ with a glitchless switch to CLK_IN as the frequency reference when
+ CLK_IN is present.
+ type: boolean
+
+ cirrus,smart-mode-clkin-only:
+ description:
+ In Smart Mode keep PLL_OUT disabled until there is clock on CLK_IN.
+ type: boolean
+
+ cirrus,bclk-invert:
+ description:
+ Invert the BCLK output.
+ type: boolean
+
+ cirrus,fsync-invert:
+ description:
+ Invert the FSYNC output.
+ type: boolean
+
+ cirrus,fsync-duty-cycles:
+ description:
+ FSYNC output duty. The number of BCLK cycles that FSYNC is asserted.
+ If not present the duty is 50%.
+ This is determined by the type of audio port that is being clocked.
+ For example TDM formats typically define FSYNC is a pulse of 1 BCLK
+ at the start of a frame.
+ $ref: /schemas/types.yaml#/definitions/uint32
+ enum: [ 1, 2, 4, 8, 16, 32 ]
+
+required:
+ - compatible
+ - reg
+ - '#clock-cells'
+ - clocks
+ - clock-names
+ - vdd-supply
+
+allOf:
+ - if:
+ required:
+ - cirrus,smart-mode-clkin-only
+ then:
+ required:
+ - cirrus,smart-mode
+
+ # Require clk_in for smart mode
+ - if:
+ required:
+ - cirrus,smart-mode
+ then:
+ properties:
+ clocks:
+ minItems: 2
+ clock-names:
+ minItems: 2
+
+additionalProperties: false
+
+examples:
+ - |
+ /* Smart mode */
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ clock-controller@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&xtl_clk>, <&sync_clock>;
+ clock-names = "ref_clk_in", "clk_in";
+ vdd-supply = <&vreg>;
+ cirrus,smart-mode;
+ };
+ };
+
+ - |
+ /* Smart mode with PLL_OUT suppressed until CLK_IN is available */
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ clock-controller@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&xtl_clk>, <&sync_clock>;
+ clock-names = "ref_clk_in", "clk_in";
+ vdd-supply = <&vreg>;
+ cirrus,smart-mode;
+ cirrus,smart-mode-clkin-only;
+ };
+ };
+
+ - |
+ /* Manual mode using only internal oscillator */
+ intosc_clk: intosc-clk {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <12000000>;
+ };
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ clock-controller@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&intosc_clk>;
+ clock-names = "ref_clk_in";
+ vdd-supply = <&vreg>;
+ cirrus,internal-oscillator;
+ };
+ };
+
+ - |
+ /* Manual mode using internal oscillator and external clk_in */
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ clock-controller@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&intosc_clk>, <&sync_clock>;
+ clock-names = "ref_clk_in", "clk_in";
+ vdd-supply = <&vreg>;
+ cirrus,internal-oscillator;
+ };
+ };
+
+ - |
+ /* Invert FSYNC output clock */
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ clock-controller@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&intosc_clk>, <&sync_clock>;
+ clock-names = "ref_clk_in", "clk_in";
+ vdd-supply = <&vreg>;
+ cirrus,fsync-invert;
+ };
+ };
+
+ - |
+ /* Change FSYNC duty */
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ clock-controller@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&intosc_clk>, <&sync_clock>;
+ clock-names = "ref_clk_in", "clk_in";
+ vdd-supply = <&vreg>;
+ cirrus,fsync-duty-cycles = <1>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 15011f5752a9..fc91c4389237 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6213,6 +6213,13 @@ F: sound/hda/codecs/side-codecs/cs*
F: sound/hda/codecs/side-codecs/hda_component*
F: sound/soc/codecs/cs*
+CIRRUS LOGIC CLOCK DRIVERS
+M: patches@opensource.cirrus.com
+L: patches@opensource.cirrus.com
+S: Supported
+F: Documentation/devicetree/bindings/clock/cirrus,cs2600*
+F: include/dt-bindings/clock/cirrus,cs2600*
+
CIRRUS LOGIC HAPTIC DRIVERS
M: James Ogletree <jogletre@opensource.cirrus.com>
M: Fred Treven <fred.treven@cirrus.com>
diff --git a/include/dt-bindings/clock/cirrus,cs2600-clock.h b/include/dt-bindings/clock/cirrus,cs2600-clock.h
new file mode 100644
index 000000000000..bde505e17813
--- /dev/null
+++ b/include/dt-bindings/clock/cirrus,cs2600-clock.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause*/
+/*
+ * CS2600 clock driver
+ *
+ * Copyright (C) 2023-2025 Cirrus Logic, Inc. and
+ * Cirrus Logic International Semiconductor Ltd.
+ */
+
+#ifndef __DT_BINDINGS_CLOCK_CS2600_H
+#define __DT_BINDINGS_CLOCK_CS2600_H
+
+/* Clock output indexes */
+#define CS2600_PLL_OUT 0
+#define CS2600_CLK_OUT 1
+#define CS2600_BCLK_OUT 2
+#define CS2600_FSYNC_OUT 3
+
+#endif /* __DT_BINDINGS_CLOCK_CS2600_H */
--
2.47.3
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v6 2/3] clk: cs2600: Add Fractional-N clock driver
2026-06-30 15:55 [PATCH v6 0/3] Cirrus Logic CS2600 clock device Richard Fitzgerald
2026-06-30 15:55 ` [PATCH v6 1/3] dt-bindings: clock: cs2600: Add support for the CS2600 Richard Fitzgerald
@ 2026-06-30 15:55 ` Richard Fitzgerald
2026-06-30 16:11 ` sashiko-bot
2026-06-30 16:59 ` Uwe Kleine-König
2026-06-30 15:55 ` [PATCH v6 3/3] clk: cs2600: Add KUnit test for CS2600 driver Richard Fitzgerald
2 siblings, 2 replies; 10+ messages in thread
From: Richard Fitzgerald @ 2026-06-30 15:55 UTC (permalink / raw)
To: mturquette, sboyd, bmasney, robh, krzk+dt, conor+dt
Cc: linux-clk, devicetree, linux-kernel, patches
From: Paul Handrigan <paulha@opensource.cirrus.com>
Add driver for the Cirrus Logic CS2600 fractional-N clock synthesizer
and multiplier.
The CS2600 is a system-clocking device using a hybrid fractional-N analog
PLL and a digital frequency-locked loop (FLL). The CS2600 enables frequency
synthesis and clock generation from a stable timing reference clock. The
device can generate low-jitter clocks between 6 MHz and 75 MHz from a noisy
clock reference between 50 Hz and 30 MHz, or from a stable clock reference
between 8 MHz and 75 MHz.
The timing reference clock (REF_CLK_IN) can be an external clock source
or the internal oscillator. This clock drives the PLL and is always
required to generate output clocks, even if it is not used as the frequency
reference.
The frequency reference source can be either REF_CLK_IN or an external
clock on CLK_IN.
The PLL_OUT clock frequency is a ratio of the chosen frequency reference.
The ratio is a fixed-point number of either 12.20 or 20.12 precision. The
lower-precision high-multiplication 20.12 ratios are only available when
CLK_IN is the frequency reference.
In smart mode the CS2600 will synthesize the output based on REF_CLK_IN
until a clock is present on CLK_IN; it then performs a glitchless switch
to CLK_IN as the frequency reference. A variant of smart mode
automatically enables the PLL output only when CLK_IN becomes available.
Three output pins are derived from PLL_OUT:
CLK_OUT: Gated output from PLL_OUT.
BCLK_OUT: Divided from PLL_OUT.
FSYNC_OUT: Divided from PLL_OUT.
The BCLK_OUT and FSYNC_OUT have different sets of available divide ratios
intended to provide the typical frequency relation between bit clock and
frame clock on I2S/TDM audio interfaces. They can also be inverted with
respect to CLK_OUT to support various formats of serial audio data.
FSYNC_OUT has the additional feature that it can be either a 50:50 duty
cycle or a pulse of a programmable number of BCLK cycles.
This configuration is modeled in the driver as:
+------------+
REF_CLK_IN ----| Hybrid PLL | PLL_OUT +--------+
| + FLL |-----+------| GATE |--------- CLK_OUT
CLK_IN ----| | | +--------+
+------------+ |
| +---------+
+------| DIVIDER |-------- BCLK_OUT
| +---------+
|
| +---------+
+------| DIVIDER |-------- FSYNC_OUT
+---------+
OF properties define the hardware-level configuration to match the
hardware that the CS2600 is driving.
The CS2600_ERR_xxx register bits are not used in the driver but are
defined in the header file as a convenience for anyone debugging a
system.
Signed-off-by: Paul Handrigan <paulha@opensource.cirrus.com>
Co-developed-by: Richard Fitzgerald <rf@opensource.cirrus.com>
Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
Changes in V6:
- Merged the functions to calculate synth mode and mult mode ratios. The
math is the same, only the fixed-point precision is different. The new
cs2600_calc_ratio() function calculates at 20:20 fixed point and then
converts to 20:12 if the value is too large for 12:20.
This also simplifies the calling code because it doesn't need all the
checks of which ratio function it should be calling.
- Re-worked the rounding of calculated rates so that they didn't drift
away from the best rate when the clock core passed the rounded rate from
determine_rate() back into set_rate().
- Changed various parts of the recalc_rate()/determine_rate()/set_rate()
functions that relied on the state of the other clock registers to make
decisions. There's no guarantee whether those other clocks have been
configured yet so the decision making could be invalid.
- Removed the use of different ratio slots depending on the mode. The
The CS2600 doesn't care which slots are used for synth and mult ratio.
Using fixed slots simplifies the code.
- Split the large cs2600_pll_out_prepare() into helper functions.
- Request ref_clk when starting the PLL. It is the system clock of the
CS2600 so it is always required to clock the chip even if it isn't the
frequency reference parent. In previous versions ref_clk was only
enabled by being the parent, which meant the CS2600 wasn't clocked when
using mult mode with clk_in as the frequency reference parent.
- Fix cs2600_pll_out_set_parent() to properly reconfigure the PLL when
changing parents. The parent selection isn't a simple mux, it changes
PLL mode and selected ratio registers. Previously the mode bit was
written but the mode ratio registers weren't updated to match.
- Factor out the BCLK and FSYNC divider lookup into a single function. The
lookup code is the same apart from the divide value that each register
field value represents. The divide values are provided as an array.
- Don't attempt to set a best_parent_rate when setting BCLK/FSYNC rate.
The parent (CLK_OUT) rate is not arbitrary, it must match the requirements
of the device being clocked, and only the consumer driver will know
what is correct.
- Set CLK_SET_PARENT_GATE on the PLL. Its configuration cannot be updated
atomically, so it must be stopped to reconfigure it.
- Split several functions into separate functions for calculating a value
and get/set the register.
- Use FIELD_PREP() and FIELD_GET() instead of multiple custom macros.
- Fix inverted register unfreeze and freeze that meant the bits affected
by FREEZE_EN were not actually updated.
- Use a DEFINE_GUARD to implement the register field unfreeze.
- Rename cs2600_ref_clk_bound_rate() to cs2600_ref_clk_set_divider() to
indicate it's actually setting the rate, not just finding a rounded value.
- Remove inappropriate use of in_range() that required some confusing
math to pass the correct value into in_range(). It's clearer to do
normal min/max comparison.
- Use DIV_ROUND_UP_ULL() to convert PPM to a frequency delta instead of
open-coding it.
- Initialize all the clocks using a single common loop.
- Use clk_init_data.parent_hws to set FSYNC, BCLK and CLK_OUT parent as
PLL_OUT, instead of looking up PLL_OUT by name and setting FSYNC, BCLK
and CLK_OUT parent to that name.
- Smart mode selection is now two boolean properties instead of an enum
property. One property select smart mode, the other property enables
suppression of PLL_OUT until there is clock on CLK_IN.
- Added properties to invert FSYNC and BCLK relative to CLK_OUT.
- Added property to set the duty cycle of FSYNC (required when clocking
TDM audio formats).
- Use match_string() instead of a string of strcmp() to lookup
cirrus,aux1-output-source value.
- Use of the internal oscillator is now explictly flagged by the presence
of the cirrus,internal-oscillator property. Previously the driver
inferred it from the absence of a ref_clk in clock-names.
- More error checking.
Changes before V6:
Sorry, the previously upstreamed versions don't have a changelist for
me to copy into here.
MAINTAINERS | 1 +
drivers/clk/Kconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/cirrus/Kconfig | 10 +
drivers/clk/cirrus/Makefile | 3 +
drivers/clk/cirrus/clk-cs2600.c | 1274 +++++++++++++++++++++++++++++++
drivers/clk/cirrus/clk-cs2600.h | 163 ++++
7 files changed, 1453 insertions(+)
create mode 100644 drivers/clk/cirrus/Kconfig
create mode 100644 drivers/clk/cirrus/Makefile
create mode 100644 drivers/clk/cirrus/clk-cs2600.c
create mode 100644 drivers/clk/cirrus/clk-cs2600.h
diff --git a/MAINTAINERS b/MAINTAINERS
index fc91c4389237..54407ed4b338 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6218,6 +6218,7 @@ M: patches@opensource.cirrus.com
L: patches@opensource.cirrus.com
S: Supported
F: Documentation/devicetree/bindings/clock/cirrus,cs2600*
+F: drivers/clk/cirrus/*
F: include/dt-bindings/clock/cirrus,cs2600*
CIRRUS LOGIC HAPTIC DRIVERS
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 1717ce75a907..e576c6cd33f4 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -509,6 +509,7 @@ source "drivers/clk/actions/Kconfig"
source "drivers/clk/analogbits/Kconfig"
source "drivers/clk/aspeed/Kconfig"
source "drivers/clk/bcm/Kconfig"
+source "drivers/clk/cirrus/Kconfig"
source "drivers/clk/eswin/Kconfig"
source "drivers/clk/hisilicon/Kconfig"
source "drivers/clk/imgtec/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index cc108a75a900..b1e3bb794d7f 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -119,6 +119,7 @@ obj-$(CONFIG_ARCH_ARTPEC) += axis/
obj-$(CONFIG_ARC_PLAT_AXS10X) += axs10x/
obj-y += bcm/
obj-$(CONFIG_ARCH_BERLIN) += berlin/
+obj-y += cirrus/
obj-$(CONFIG_ARCH_DAVINCI) += davinci/
obj-$(CONFIG_COMMON_CLK_ESWIN) += eswin/
obj-$(CONFIG_ARCH_HISI) += hisilicon/
diff --git a/drivers/clk/cirrus/Kconfig b/drivers/clk/cirrus/Kconfig
new file mode 100644
index 000000000000..5674d6554f96
--- /dev/null
+++ b/drivers/clk/cirrus/Kconfig
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config COMMON_CLK_CS2600
+ tristate "Clock driver for CS2600 Fractional-N Clock Synthesizer & Clock Multiplier"
+ depends on I2C
+ depends on OF
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for the CS2600 clock synthesizer
+ and multiplier.
diff --git a/drivers/clk/cirrus/Makefile b/drivers/clk/cirrus/Makefile
new file mode 100644
index 000000000000..182612dbb96a
--- /dev/null
+++ b/drivers/clk/cirrus/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_COMMON_CLK_CS2600) += clk-cs2600.o
diff --git a/drivers/clk/cirrus/clk-cs2600.c b/drivers/clk/cirrus/clk-cs2600.c
new file mode 100644
index 000000000000..73937ac868fc
--- /dev/null
+++ b/drivers/clk/cirrus/clk-cs2600.c
@@ -0,0 +1,1274 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// CS2600 -- CIRRUS LOGIC Fractional-N Clock Synthesizer & Clock Multiplier
+//
+// Copyright (C) 2024-2026 Cirrus Logic, Inc. and
+// Cirrus Logic International Semiconductor Ltd.
+
+#include <dt-bindings/clock/cirrus,cs2600-clock.h>
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/container_of.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/math.h>
+#include <linux/math64.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/string.h>
+
+#include "clk-cs2600.h"
+
+#define hw_to_cs2600_clk(_hw) container_of(_hw, struct cs2600_clk_hw, hw)
+
+static const struct reg_default cs2600_reg[] = {
+ { CS2600_PLL_CFG1, 0x0080 },
+ { CS2600_PLL_CFG2, 0x0008 },
+ { CS2600_RATIO1_1, 0x0000 },
+ { CS2600_RATIO1_2, 0x0000 },
+ { CS2600_RATIO2_1, 0x0000 },
+ { CS2600_RATIO2_2, 0x0000 },
+ { CS2600_PLL_CFG3, 0x0000 },
+ { CS2600_OUTPUT_CFG1, 0x0000 },
+ { CS2600_OUTPUT_CFG2, 0x0000 },
+ { CS2600_PHASE_ALIGNMENT_CFG1, 0x0000 },
+};
+
+static bool cs2600_read_and_write_reg(unsigned int reg)
+{
+ switch (reg) {
+ case CS2600_PLL_CFG1:
+ case CS2600_PLL_CFG2:
+ case CS2600_RATIO1_1:
+ case CS2600_RATIO1_2:
+ case CS2600_RATIO2_1:
+ case CS2600_RATIO2_2:
+ case CS2600_PLL_CFG3:
+ case CS2600_OUTPUT_CFG1:
+ case CS2600_OUTPUT_CFG2:
+ case CS2600_PHASE_ALIGNMENT_CFG1:
+ case CS2600_UNLOCK_INDICATORS:
+ case CS2600_ERROR_STS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool cs2600_writeable_reg(struct device *dev, unsigned int reg)
+{
+ if (reg == CS2600_SW_RESET)
+ return true;
+
+ return cs2600_read_and_write_reg(reg);
+}
+
+static bool cs2600_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CS2600_DEVICE_ID1:
+ case CS2600_DEVICE_ID2:
+ return true;
+ default:
+ return cs2600_read_and_write_reg(reg);
+ }
+}
+
+static bool cs2600_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CS2600_UNLOCK_INDICATORS:
+ case CS2600_ERROR_STS:
+ case CS2600_SW_RESET:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool cs2600_precious_reg(struct device *dev, unsigned int reg)
+{
+ if (reg == CS2600_SW_RESET)
+ return true;
+
+ return false;
+}
+
+static const struct regmap_config cs2600_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 16,
+ .max_register = CS2600_MAX_REGISTER,
+ .reg_defaults = cs2600_reg,
+ .num_reg_defaults = ARRAY_SIZE(cs2600_reg),
+ .readable_reg = cs2600_readable_reg,
+ .writeable_reg = cs2600_writeable_reg,
+ .volatile_reg = cs2600_volatile_reg,
+ .precious_reg = cs2600_precious_reg,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static void cs2600_set_freeze(struct cs2600 *cs2600)
+{
+ regmap_set_bits(cs2600->regmap, CS2600_PLL_CFG2, CS2600_FREEZE_EN);
+}
+
+static void cs2600_clear_freeze(struct cs2600 *cs2600)
+{
+ regmap_clear_bits(cs2600->regmap, CS2600_PLL_CFG2, CS2600_FREEZE_EN);
+}
+
+DEFINE_GUARD(cs2600_unfreeze, struct cs2600 *,
+ cs2600_clear_freeze(_T), cs2600_set_freeze(_T))
+
+static inline bool cs2600_is_smart_mode(struct cs2600 *cs2600)
+{
+ return (cs2600->mode == CS2600_SMART_CLKIN_ONLY_MODE ||
+ cs2600->mode == CS2600_SMART_MODE);
+}
+
+static int cs2600_ref_clk_set_divider(struct cs2600 *cs2600, unsigned long rate_in)
+{
+ unsigned int val;
+
+ if ((rate_in >= 32000000) && (rate_in <= 75000000)) {
+ val = 0x0;
+ } else if ((rate_in >= 16000000) && (rate_in <= 37500000)) {
+ val = 0x1;
+ } else if ((rate_in >= 8000000) && (rate_in <= 18750000)) {
+ val = 0x2;
+ } else {
+ dev_err(cs2600->dev, "Invalid refclk %lu\n", rate_in);
+ return -EINVAL;
+ }
+
+ return regmap_update_bits(cs2600->regmap, CS2600_PLL_CFG3,
+ CS2600_REF_CLK_IN_DIV_MASK,
+ FIELD_PREP(CS2600_REF_CLK_IN_DIV_MASK, val));
+}
+
+static unsigned long cs2600_get_freq_variation(unsigned long freq,
+ unsigned long ppm)
+{
+ return (unsigned long)DIV_ROUND_UP_ULL((u64)freq * ppm, 1000000);
+}
+
+static int cs2600_calc_ratio(struct cs2600 *cs2600,
+ unsigned long rate,
+ unsigned long parent_rate,
+ unsigned int *ratio_out,
+ bool *hi_res)
+{
+ u64 rate_shifted = (u64)rate;
+ u64 ratio;
+
+ if (parent_rate == 0)
+ return -EINVAL;
+
+ if ((rate < CS2600_CLK_OUT_MIN) || (rate > CS2600_CLK_OUT_MAX))
+ return -EINVAL;
+
+ /* Try high-resolution mode first */
+ *hi_res = true;
+ rate_shifted <<= CS2600_12_20_SHIFT;
+ ratio = div_u64(rate_shifted, parent_rate);
+
+ if (ratio > CS2600_12_20_RATIO_MAX) {
+ *hi_res = false;
+ ratio >>= (CS2600_12_20_SHIFT - CS2600_20_12_SHIFT);
+
+ if (ratio > CS2600_20_12_RATIO_MAX) {
+ dev_err(cs2600->dev, "Ratio %lu:%lu too large\n", parent_rate, rate);
+ return -EINVAL;
+ }
+ }
+
+ *ratio_out = ratio;
+
+ return 0;
+}
+
+static unsigned int cs2600_calc_smart_synth_ratio(struct cs2600 *cs2600,
+ unsigned long clkin_rate,
+ unsigned int m_ratio,
+ bool hi_res,
+ unsigned long refclk_rate)
+{
+ u64 m_rate_32_20 = (u64)clkin_rate * m_ratio;
+
+ /* If low-res adjust 32.12 value to 32.20 */
+ if (!hi_res)
+ m_rate_32_20 <<= (CS2600_12_20_SHIFT - CS2600_20_12_SHIFT);
+
+ return (unsigned int)div_u64(m_rate_32_20, refclk_rate);
+}
+
+static unsigned long cs2600_calc_rounded_integer_rate(struct cs2600 *cs2600,
+ unsigned long parent_rate,
+ unsigned long target_rate)
+{
+ unsigned int ratio;
+ bool hi_res;
+ u64 fout;
+ int ret;
+
+ ret = cs2600_calc_ratio(cs2600, target_rate, parent_rate, &ratio, &hi_res);
+ if (ret)
+ return 0;
+
+ fout = (u64)ratio * parent_rate;
+ if (hi_res)
+ fout >>= CS2600_12_20_SHIFT;
+ else
+ fout >>= CS2600_20_12_SHIFT;
+
+ /*
+ * If it's fractional round to the next higher integer, so that if
+ * it is passed to set_rate() it will produce the same ratio and
+ * target frequency we just calculated.
+ */
+ if (fout != target_rate)
+ fout++;
+
+ if (fout < CS2600_CLK_OUT_MIN)
+ return 0;
+
+ return fout;
+}
+
+static unsigned long cs2600_pll_out_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct cs2600 *cs2600 = clk_hw->priv;
+
+ if ((parent_rate == 0) || (cs2600->pll_target_rate == 0))
+ return 0;
+
+ if (parent_rate == cs2600->pll_parent_rate)
+ return cs2600->pll_target_rate;
+
+ return cs2600_calc_rounded_integer_rate(cs2600, parent_rate, cs2600->pll_target_rate);
+}
+
+static int cs2600_pll_out_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct cs2600 *cs2600 = clk_hw->priv;
+ unsigned long out_rate;
+
+ out_rate = cs2600_calc_rounded_integer_rate(cs2600, req->best_parent_rate, req->rate);
+ if (out_rate == 0)
+ return -EINVAL;
+
+ req->rate = out_rate;
+
+ return 0;
+}
+
+static int cs2600_set_resolution_mode(struct cs2600 *cs2600, bool hi_res)
+{
+ unsigned int val = 0;
+
+ if (hi_res)
+ val = CS2600_RATIO_CFG;
+
+ return regmap_update_bits(cs2600->regmap, CS2600_PLL_CFG3, CS2600_RATIO_CFG, val);
+}
+
+enum cs2600_ratio_slot {
+ CS2600_RATIO_SLOT_1,
+ CS2600_RATIO_SLOT_2,
+};
+
+static const struct {
+ u8 h;
+ u8 l;
+} cs2600_ratio_regs[] = {
+ [CS2600_RATIO_SLOT_1] = { .h = CS2600_RATIO1_1, .l = CS2600_RATIO1_2 },
+ [CS2600_RATIO_SLOT_2] = { .h = CS2600_RATIO2_1, .l = CS2600_RATIO2_2 },
+};
+
+static int cs2600_write_ratio(struct cs2600 *cs2600, enum cs2600_ratio_slot slot,
+ unsigned int ratio)
+{
+ int ret;
+
+ ret = regmap_write(cs2600->regmap, cs2600_ratio_regs[slot].h, ratio >> 16);
+ if (ret)
+ return ret;
+
+ return regmap_write(cs2600->regmap, cs2600_ratio_regs[slot].l, ratio & 0xFFFF);
+}
+
+static int cs2600_pll_out_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct regmap *regmap = clk_hw->priv->regmap;
+ struct cs2600 *cs2600 = clk_hw->priv;
+ unsigned int ratio, smart_s_ratio;
+ bool hi_res;
+ int ret;
+
+ ret = cs2600_calc_ratio(cs2600, rate, parent_rate, &ratio, &hi_res);
+ if (ret)
+ return ret;
+
+ switch (cs2600->mode) {
+ case CS2600_SMART_MODE:
+ /*
+ * Calc synth ratio to match the actual mult output frequency
+ * to minimise the frequency change when the PLL switches from
+ * synth to mult.
+ */
+ smart_s_ratio = cs2600_calc_smart_synth_ratio(cs2600, parent_rate,
+ ratio, hi_res,
+ cs2600->refclk_rate);
+ ret = cs2600_write_ratio(cs2600, CS2600_RATIO_SLOT_2, smart_s_ratio);
+ if (ret < 0)
+ return ret;
+ break;
+ case CS2600_SMART_CLKIN_ONLY_MODE:
+ ret = cs2600_write_ratio(cs2600, CS2600_RATIO_SLOT_2, 0);
+ if (ret < 0)
+ return ret;
+ break;
+ default:
+ /* Low-resolution ratios can only be used in mult mode */
+ ret = regmap_test_bits(regmap, CS2600_PLL_CFG2, CS2600_PLL_MODE_SEL);
+ if (ret < 0)
+ return ret;
+
+ if (!hi_res && !ret)
+ return -EINVAL;
+
+ break;
+ }
+
+ ret = cs2600_write_ratio(cs2600, CS2600_RATIO_SLOT_1, ratio);
+ if (ret < 0)
+ return ret;
+
+ ret = cs2600_set_resolution_mode(cs2600, hi_res);
+ if (ret < 0)
+ return ret;
+
+ cs2600->pll_target_rate = rate;
+ cs2600->pll_parent_rate = parent_rate;
+
+ return 0;
+}
+
+static int cs2600_pll_out_is_prepared(struct clk_hw *hw)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct regmap *regmap = clk_hw->priv->regmap;
+
+ return regmap_test_bits(regmap, CS2600_PLL_CFG1, CS2600_PLL_EN1);
+}
+
+static int cs2600_enable_pll(struct cs2600 *cs2600, bool enable)
+{
+ int ret;
+
+ scoped_guard(cs2600_unfreeze, cs2600) {
+ ret = regmap_update_bits(cs2600->regmap, CS2600_PLL_CFG1,
+ CS2600_PLL_EN1,
+ FIELD_PREP(CS2600_PLL_EN1, !!enable));
+ if (ret < 0)
+ return ret;
+ }
+
+ return regmap_update_bits(cs2600->regmap, CS2600_PLL_CFG2, CS2600_PLL_EN2,
+ FIELD_PREP(CS2600_PLL_EN2, !!enable));
+}
+
+static int cs2600_wait_for_pll_lock(struct cs2600 *cs2600)
+{
+ int i, ret;
+
+ usleep_range(1000, 1100);
+ for (i = 0; i < CS2600_LOCK_ATTEMPTS_MAX; i++) {
+ ret = regmap_test_bits(cs2600->regmap, CS2600_UNLOCK_INDICATORS,
+ CS2600_F_UNLOCK_STICKY);
+ if (ret < 0)
+ return ret;
+
+ if (ret == 0)
+ return 0;
+
+ ret = regmap_write(cs2600->regmap, CS2600_UNLOCK_INDICATORS,
+ CS2600_CLEAR_INDICATORS);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(200, 300);
+ }
+
+ dev_err(cs2600->dev, "PLL did not lock\n");
+
+ return -ETIMEDOUT;
+}
+
+static int _cs2600_pll_out_prepare(struct cs2600 *cs2600)
+{
+ int ret;
+
+ ret = cs2600_enable_pll(cs2600, true);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * clkin-only mode configures the PLL so that it only starts
+ * when clock is present on CLK_IN. So don't wait for lock here.
+ */
+ if (cs2600->mode == CS2600_SMART_CLKIN_ONLY_MODE)
+ return 0;
+
+ ret = regmap_write(cs2600->regmap, CS2600_UNLOCK_INDICATORS,
+ CS2600_CLEAR_INDICATORS);
+ if (!ret)
+ ret = cs2600_wait_for_pll_lock(cs2600);
+
+ if (ret < 0)
+ cs2600_enable_pll(cs2600, false);
+
+ return ret;
+}
+
+static int cs2600_pll_out_prepare(struct clk_hw *hw)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct regmap *regmap = clk_hw->priv->regmap;
+ struct cs2600 *cs2600 = clk_hw->priv;
+ int ret;
+
+ ret = regmap_test_bits(regmap, CS2600_PLL_CFG1, CS2600_PLL_EN1);
+ if (ret < 0)
+ return ret;
+
+ if (ret)
+ return 0;
+
+ /* REF_CLK is always required to clock the PLL */
+ if (cs2600->ref_clk) {
+ ret = clk_prepare_enable(cs2600->ref_clk);
+ if (ret < 0) {
+ dev_err(cs2600->dev, "Failed to enable ref_clk: %d\n", ret);
+ return ret;
+ }
+ }
+
+ ret = _cs2600_pll_out_prepare(cs2600);
+ if (ret < 0) {
+ if (cs2600->ref_clk)
+ clk_disable_unprepare(cs2600->ref_clk);
+
+ return ret;
+ }
+
+ return 0;
+}
+
+
+static void cs2600_pll_out_unprepare(struct clk_hw *hw)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct regmap *regmap = clk_hw->priv->regmap;
+ struct cs2600 *cs2600 = clk_hw->priv;
+
+ scoped_guard(cs2600_unfreeze, cs2600)
+ regmap_clear_bits(regmap, CS2600_PLL_CFG1, CS2600_PLL_EN1);
+
+ regmap_clear_bits(regmap, CS2600_PLL_CFG2, CS2600_PLL_EN2);
+
+ if (cs2600->ref_clk)
+ clk_disable_unprepare(cs2600->ref_clk);
+}
+
+static int cs2600_pll_out_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct regmap *regmap = clk_hw->priv->regmap;
+ struct cs2600 *cs2600 = clk_hw->priv;
+ struct clk_hw *parent_clk_hw;
+ unsigned long parent_rate;
+ unsigned int old_mode_sel;
+ int ret;
+
+ parent_clk_hw = clk_hw_get_parent_by_index(hw, index);
+ if (!parent_clk_hw)
+ return -EINVAL;
+
+ parent_rate = clk_hw_get_rate(parent_clk_hw);
+
+ /* CLK_IN is the only parent in smart mode */
+ if (cs2600_is_smart_mode(cs2600))
+ return 0;
+
+ if (!FIELD_FIT(CS2600_PLL_MODE_SEL, index)) {
+ dev_err(cs2600->dev, "Invalid parent index %d", index);
+ return -EINVAL;
+ }
+
+ ret = regmap_read(regmap, CS2600_PLL_CFG2, &old_mode_sel);
+ if (ret) {
+ dev_err(cs2600->dev, "Read PLL_CFG2 failed: %d\n", ret);
+ return ret;
+ }
+
+ scoped_guard(cs2600_unfreeze, cs2600) {
+ ret = regmap_update_bits(regmap, CS2600_PLL_CFG2, CS2600_PLL_MODE_SEL,
+ index);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (cs2600->pll_target_rate == 0)
+ return 0;
+
+ ret = cs2600_pll_out_set_rate(hw, cs2600->pll_target_rate, parent_rate);
+ if (ret) {
+ scoped_guard(cs2600_unfreeze, cs2600) {
+ regmap_update_bits(regmap, CS2600_PLL_CFG2,
+ CS2600_PLL_MODE_SEL, old_mode_sel);
+ }
+
+ return ret;
+ }
+
+ return 0;
+}
+
+static u8 cs2600_pll_out_get_parent(struct clk_hw *hw)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct regmap *regmap = clk_hw->priv->regmap;
+ struct cs2600 *cs2600 = clk_hw->priv;
+ int ret;
+
+ /* CLK_IN is the only parent in smart mode */
+ if (cs2600_is_smart_mode(cs2600))
+ return 0;
+
+ ret = regmap_test_bits(regmap, CS2600_PLL_CFG2, CS2600_PLL_MODE_SEL);
+ if (ret < 0)
+ return 0;
+
+ return (u8)ret;
+}
+
+static int cs2600_clk_out_prepare(struct clk_hw *hw)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct regmap *regmap = clk_hw->priv->regmap;
+ struct cs2600 *cs2600 = clk_hw->priv;
+
+ guard(cs2600_unfreeze)(cs2600);
+ return regmap_clear_bits(regmap, CS2600_PLL_CFG1, CS2600_CLK_OUT_DIS);
+}
+
+static void cs2600_clk_out_unprepare(struct clk_hw *hw)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct regmap *regmap = clk_hw->priv->regmap;
+ struct cs2600 *cs2600 = clk_hw->priv;
+
+ guard(cs2600_unfreeze)(cs2600);
+ regmap_set_bits(regmap, CS2600_PLL_CFG1, CS2600_CLK_OUT_DIS);
+}
+
+static int cs2600_clk_out_is_prepared(struct clk_hw *hw)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct regmap *regmap = clk_hw->priv->regmap;
+ int ret;
+
+ ret = regmap_test_bits(regmap, CS2600_PLL_CFG1, CS2600_CLK_OUT_DIS);
+ if (ret < 0)
+ return ret;
+
+ return !ret;
+}
+
+static const u16 cs2600_bclk_div[] = {
+ 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48,
+ 0 /* terminator */
+};
+
+static int cs2600_get_clk_div_index(const u16 *divs, unsigned int rate, unsigned int parent_rate)
+{
+ unsigned long calc_rate, freq_var;
+ int i;
+
+ freq_var = cs2600_get_freq_variation(rate, CS2600_20_12_PPM);
+
+ for (i = 0; divs[i]; i++) {
+ calc_rate = parent_rate / divs[i];
+ if (in_range(rate, calc_rate - freq_var, 2 * freq_var))
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+static int cs2600_bclk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct regmap *regmap = clk_hw->priv->regmap;
+ struct cs2600 *cs2600 = clk_hw->priv;
+ int div_val;
+
+ if (rate > CS2600_BCLK_OUT_MAX || rate < CS2600_BCLK_OUT_MIN) {
+ dev_err(cs2600->dev, "Invalid bclk rate %lu", rate);
+ return -EINVAL;
+ }
+
+ if (parent_rate > CS2600_CLK_OUT_MAX || parent_rate < CS2600_CLK_OUT_MIN) {
+ dev_err(cs2600->dev, "Invalid clk_out rate %lu", parent_rate);
+ return -EINVAL;
+ }
+
+ div_val = cs2600_get_clk_div_index(cs2600_bclk_div, rate, parent_rate);
+ if (div_val < 0) {
+ dev_err(cs2600->dev, "Cannot set bclk %lu from parent rate %lu\n", rate,
+ parent_rate);
+
+ return -EINVAL;
+ }
+
+ return regmap_update_bits(regmap, CS2600_OUTPUT_CFG1, CS2600_BCLK_DIV_MASK,
+ FIELD_PREP(CS2600_BCLK_DIV_MASK, div_val));
+}
+
+static int cs2600_bclk_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct cs2600 *cs2600 = clk_hw->priv;
+
+ if (req->rate > CS2600_BCLK_OUT_MAX ||
+ req->rate < CS2600_BCLK_OUT_MIN) {
+ dev_err(cs2600->dev, "Invalid bclk rate %lu", req->rate);
+ return -EINVAL;
+ }
+
+ if (cs2600_get_clk_div_index(cs2600_bclk_div, req->rate, req->best_parent_rate) < 0) {
+ if (req->best_parent_rate)
+ dev_err(cs2600->dev, "BCLK_OUT cannot be derived from the parent rate");
+
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static unsigned long cs2600_bclk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct regmap *regmap = clk_hw->priv->regmap;
+ struct cs2600 *cs2600 = clk_hw->priv;
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(regmap, CS2600_OUTPUT_CFG1, &val);
+ if (ret) {
+ dev_err(cs2600->dev, "Read bclk divider failed: %d\n", ret);
+ return 0;
+ }
+
+ val = FIELD_GET(CS2600_BCLK_DIV_MASK, val);
+ if ((val < ARRAY_SIZE(cs2600_bclk_div)) && cs2600_bclk_div[val])
+ return (parent_rate / cs2600_bclk_div[val]);
+
+ dev_err(cs2600->dev, "Cannot find bclk divide value for index=%#x\n", val);
+
+ return 0;
+}
+
+static int cs2600_bclk_prepare(struct clk_hw *hw)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct regmap *regmap = clk_hw->priv->regmap;
+ struct cs2600 *cs2600 = clk_hw->priv;
+ int ret;
+
+ ret = regmap_set_bits(regmap, CS2600_PHASE_ALIGNMENT_CFG1,
+ CS2600_PHASE_ALIGN_EN | CS2600_PHASE_ALIGN_STB_EN);
+ if (ret < 0)
+ return ret;
+
+ guard(cs2600_unfreeze)(cs2600);
+ return regmap_clear_bits(regmap, CS2600_OUTPUT_CFG1, CS2600_BCLK_OUT_DIS);
+}
+
+static void cs2600_bclk_unprepare(struct clk_hw *hw)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct regmap *regmap = clk_hw->priv->regmap;
+ struct cs2600 *cs2600 = clk_hw->priv;
+ int ret;
+
+ ret = regmap_test_bits(regmap, CS2600_OUTPUT_CFG1, CS2600_FSYNC_OUT_DIS);
+ if (ret > 0)
+ regmap_clear_bits(regmap, CS2600_PHASE_ALIGNMENT_CFG1,
+ CS2600_PHASE_ALIGN_EN |
+ CS2600_PHASE_ALIGN_STB_EN);
+
+ guard(cs2600_unfreeze)(cs2600);
+ regmap_set_bits(regmap, CS2600_OUTPUT_CFG1, CS2600_BCLK_OUT_DIS);
+}
+
+static int cs2600_bclk_is_prepared(struct clk_hw *hw)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct regmap *regmap = clk_hw->priv->regmap;
+ int ret;
+
+ ret = regmap_test_bits(regmap, CS2600_OUTPUT_CFG1, CS2600_BCLK_OUT_DIS);
+ if (ret < 0)
+ return ret;
+
+ return !ret;
+}
+
+static const u16 cs2600_fsync_div[] = {
+ 16, 32, 64, 128, 256, 512, 1024, 192, 384, 768, 1536, 576, 1152,
+ 0 /* terminator */
+};
+
+static int cs2600_fsync_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct regmap *regmap = clk_hw->priv->regmap;
+ struct cs2600 *cs2600 = clk_hw->priv;
+ int div_val;
+
+ if (rate > CS2600_FSYNC_OUT_MAX || rate < CS2600_FSYNC_OUT_MIN) {
+ dev_err(cs2600->dev, "Invalid fsync rate %lu", rate);
+ return -EINVAL;
+ }
+
+ if (parent_rate > CS2600_CLK_OUT_MAX
+ || parent_rate < CS2600_CLK_OUT_MIN) {
+ dev_err(cs2600->dev, "Invalid clk_out rate %lu", parent_rate);
+ return -EINVAL;
+ }
+
+ div_val = cs2600_get_clk_div_index(cs2600_fsync_div, rate, parent_rate);
+ if (div_val < 0) {
+ dev_err(cs2600->dev, "Cannot set fsync %lu from parent rate %lu\n",
+ rate, parent_rate);
+
+ return -EINVAL;
+ }
+
+ return regmap_update_bits(regmap, CS2600_OUTPUT_CFG1, CS2600_FSYNC_DIV_MASK,
+ FIELD_PREP(CS2600_FSYNC_DIV_MASK, div_val));
+}
+
+static int cs2600_fsync_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct cs2600 *cs2600 = clk_hw->priv;
+
+ if (req->rate > CS2600_FSYNC_OUT_MAX
+ || req->rate < CS2600_FSYNC_OUT_MIN) {
+ dev_err(cs2600->dev, "Invalid fsync rate %lu", req->rate);
+ return -EINVAL;
+ }
+
+ if (cs2600_get_clk_div_index(cs2600_fsync_div, req->rate, req->best_parent_rate) < 0) {
+ if (req->best_parent_rate)
+ dev_err(cs2600->dev, "FSYNC_OUT cannot be derived from the parent rate");
+
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static unsigned long cs2600_fsync_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct regmap *regmap = clk_hw->priv->regmap;
+ struct cs2600 *cs2600 = clk_hw->priv;
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(regmap, CS2600_OUTPUT_CFG1, &val);
+ if (ret) {
+ dev_err(cs2600->dev, "Read fsync divider failed: %d\n", ret);
+ return 0;
+ }
+
+ val = FIELD_GET(CS2600_FSYNC_DIV_MASK, val);
+ if ((val < ARRAY_SIZE(cs2600_fsync_div)) && cs2600_fsync_div[val])
+ return (parent_rate / cs2600_fsync_div[val]);
+
+ dev_err(cs2600->dev, "Cannot find fsync divide value for index=%#x\n", val);
+
+ return 0;
+}
+
+static int cs2600_fsync_prepare(struct clk_hw *hw)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct regmap *regmap = clk_hw->priv->regmap;
+ struct cs2600 *cs2600 = clk_hw->priv;
+ int ret;
+
+ ret = regmap_set_bits(regmap, CS2600_PHASE_ALIGNMENT_CFG1,
+ CS2600_PHASE_ALIGN_EN | CS2600_PHASE_ALIGN_STB_EN);
+ if (ret < 0)
+ return ret;
+
+ guard(cs2600_unfreeze)(cs2600);
+ return regmap_clear_bits(regmap, CS2600_OUTPUT_CFG1, CS2600_FSYNC_OUT_DIS);
+}
+
+static void cs2600_fsync_unprepare(struct clk_hw *hw)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct regmap *regmap = clk_hw->priv->regmap;
+ struct cs2600 *cs2600 = clk_hw->priv;
+ int ret;
+
+ ret = regmap_test_bits(regmap, CS2600_OUTPUT_CFG1, CS2600_BCLK_OUT_DIS);
+ if (ret > 0)
+ regmap_clear_bits(regmap, CS2600_PHASE_ALIGNMENT_CFG1,
+ CS2600_PHASE_ALIGN_EN |
+ CS2600_PHASE_ALIGN_STB_EN);
+
+ guard(cs2600_unfreeze)(cs2600);
+ regmap_set_bits(regmap, CS2600_OUTPUT_CFG1, CS2600_FSYNC_OUT_DIS);
+}
+
+static int cs2600_fsync_is_prepared(struct clk_hw *hw)
+{
+ struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
+ struct regmap *regmap = clk_hw->priv->regmap;
+ int ret;
+
+ ret = regmap_test_bits(regmap, CS2600_OUTPUT_CFG1, CS2600_FSYNC_OUT_DIS);
+ if (ret < 0)
+ return ret;
+
+ return !ret;
+}
+
+struct cs2600_clk {
+ const char *name;
+ const struct clk_ops ops;
+ unsigned long flags;
+};
+
+static const struct cs2600_clk cs2600_clks[CS2600_OUT_CLK_MAX] = {
+ [CS2600_PLL_OUT] = { /* Internal clock */
+ .name = "pll_out",
+ .ops = {
+ .get_parent = cs2600_pll_out_get_parent,
+ .set_parent = cs2600_pll_out_set_parent,
+ .recalc_rate = cs2600_pll_out_recalc_rate,
+ .determine_rate = cs2600_pll_out_determine_rate,
+ .set_rate = cs2600_pll_out_set_rate,
+ .prepare = cs2600_pll_out_prepare,
+ .is_prepared = cs2600_pll_out_is_prepared,
+ .unprepare = cs2600_pll_out_unprepare,
+ },
+ .flags = CLK_SET_PARENT_GATE,
+ },
+ [CS2600_CLK_OUT] = {
+ .name = "clk_out",
+ .ops = {
+ .prepare = cs2600_clk_out_prepare,
+ .unprepare = cs2600_clk_out_unprepare,
+ .is_prepared = cs2600_clk_out_is_prepared,
+ },
+ .flags = CLK_SET_RATE_PARENT,
+ },
+ [CS2600_BCLK_OUT] = {
+ .name = "bclk_out",
+ .ops = {
+ .recalc_rate = cs2600_bclk_recalc_rate,
+ .determine_rate = cs2600_bclk_determine_rate,
+ .set_rate = cs2600_bclk_set_rate,
+ .prepare = cs2600_bclk_prepare,
+ .unprepare = cs2600_bclk_unprepare,
+ .is_prepared = cs2600_bclk_is_prepared,
+
+ },
+ },
+ [CS2600_FSYNC_OUT] = {
+ .name = "fsync_out",
+ .ops = {
+ .recalc_rate = cs2600_fsync_recalc_rate,
+ .determine_rate = cs2600_fsync_determine_rate,
+ .set_rate = cs2600_fsync_set_rate,
+ .prepare = cs2600_fsync_prepare,
+ .unprepare = cs2600_fsync_unprepare,
+ .is_prepared = cs2600_fsync_is_prepared,
+
+ },
+ },
+};
+
+static int cs2600_clk_get(struct cs2600 *cs2600)
+{
+ struct clk *clk_in, *ref_clk;
+
+ ref_clk = devm_clk_get(cs2600->dev, "ref_clk_in");
+ if (IS_ERR(ref_clk))
+ return PTR_ERR(ref_clk);
+
+ cs2600->refclk_rate = clk_get_rate(ref_clk);
+ if (cs2600->refclk_rate < 8000000 || cs2600->refclk_rate > 75000000) {
+ return dev_err_probe(cs2600->dev, -EINVAL,
+ "Invalid REFCLK Frequency %lu\n",
+ cs2600->refclk_rate);
+ }
+
+ clk_in = devm_clk_get_optional(cs2600->dev, "clk_in");
+ if (IS_ERR(clk_in))
+ return PTR_ERR(clk_in);
+
+ cs2600->ref_clk = ref_clk;
+ cs2600->clk_in = clk_in;
+
+ return 0;
+}
+
+static struct clk_hw *cs2600_of_clk_get(struct of_phandle_args *clkspec,
+ void *data)
+{
+ unsigned int index = clkspec->args[0];
+ struct cs2600 *cs2600 = data;
+
+ /* PLL_OUT is an internal clock */
+ if (index > CS2600_OUT_CLK_MAX - 1) {
+ dev_err(cs2600->dev, "Invalid clock index %d\n", index);
+ return ERR_PTR(-EINVAL);
+ }
+
+ return &cs2600->hw[index].hw;
+}
+
+static int cs2600_clk_register(struct cs2600 *cs2600)
+{
+ static const char *parent_names[2];
+ const struct clk_hw *out_clocks_parent;
+ struct clk_init_data init[ARRAY_SIZE(cs2600->hw)] = { };
+ int ret, i, n, input_num = 0;
+ const char *names[ARRAY_SIZE(cs2600_clks)];
+
+ static_assert(ARRAY_SIZE(cs2600_clks) == ARRAY_SIZE(cs2600->hw));
+
+ for (i = 0; i < ARRAY_SIZE(cs2600->hw); i++) {
+ init[i].name = cs2600_clks[i].name;
+ init[i].ops = &cs2600_clks[i].ops;
+ init[i].flags = cs2600_clks[i].flags;
+ cs2600->hw[i].hw.init = &init[i];
+ cs2600->hw[i].priv = cs2600;
+ }
+
+ n = of_property_read_string_array(cs2600->dev->of_node,
+ "clock-output-names",
+ names, ARRAY_SIZE(names));
+ for (i = 0; i < n; i++)
+ init[i].name = names[i];
+
+ /* ref_clk_in is only a parent option in manual mode */
+ if (!cs2600_is_smart_mode(cs2600))
+ parent_names[input_num++] = __clk_get_name(cs2600->ref_clk);
+
+ if (cs2600->clk_in)
+ parent_names[input_num++] = __clk_get_name(cs2600->clk_in);
+
+ init[CS2600_PLL_OUT].parent_names = parent_names;
+ init[CS2600_PLL_OUT].num_parents = input_num;
+
+ /* The parent of all out clocks is the internal pll_out */
+ out_clocks_parent = &cs2600->hw[CS2600_PLL_OUT].hw;
+ for (i = CS2600_CLK_OUT; i < ARRAY_SIZE(cs2600->hw); i++) {
+ init[i].parent_hws = &out_clocks_parent;
+ init[i].num_parents = 1;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(cs2600->hw); i++) {
+ ret = devm_clk_hw_register(cs2600->dev, &cs2600->hw[i].hw);
+ if (ret)
+ return ret;
+ }
+
+ return devm_of_clk_add_hw_provider(cs2600->dev, cs2600_of_clk_get, cs2600);
+}
+
+static int cs2600_set_mode(struct cs2600 *cs2600)
+{
+ unsigned int s_slot = 0;
+ int ret;
+
+ if (device_property_present(cs2600->dev, "cirrus,smart-mode")) {
+ cs2600->mode = CS2600_SMART_MODE;
+ s_slot = 1;
+
+ if (device_property_present(cs2600->dev, "cirrus,smart-mode-clkin-only"))
+ cs2600->mode = CS2600_SMART_CLKIN_ONLY_MODE;
+ }
+
+ ret = regmap_clear_bits(cs2600->regmap, CS2600_PLL_CFG2, CS2600_M_RATIO_SEL_MASK);
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(cs2600->regmap, CS2600_PLL_CFG1, CS2600_S_RATIO_SEL_MASK,
+ FIELD_PREP(CS2600_S_RATIO_SEL_MASK, s_slot));
+}
+
+static int cs2600_set_output_inverts(struct cs2600 *cs2600)
+{
+ unsigned int field_val = 0;
+
+ if (device_property_present(cs2600->dev, "cirrus,bclk-invert"))
+ field_val |= CS2600_BCLK_INV;
+
+ if (device_property_present(cs2600->dev, "cirrus,fsync-invert"))
+ field_val |= CS2600_FSYNC_INV;
+
+ return regmap_update_bits(cs2600->regmap, CS2600_OUTPUT_CFG1,
+ CS2600_BCLK_INV | CS2600_FSYNC_INV,
+ field_val);
+}
+
+static int cs2600_set_fsync_duty(struct cs2600 *cs2600)
+{
+ u32 val;
+ int ret;
+
+ ret = device_property_read_u32(cs2600->dev, "cirrus,fsync-duty-cycles", &val);
+ if (ret == -EINVAL)
+ return 0;
+
+ if (ret < 0)
+ return ret;
+
+ if ((val < CS2600_FSYNC_MIN_CYCLES) || (val > CS2600_FSYNC_MAX_CYCLES) ||
+ !is_power_of_2(val))
+ return dev_err_probe(cs2600->dev, -EINVAL, "Invalid FSYNC duty cycle %u\n", val);
+
+ return regmap_update_bits(cs2600->regmap, CS2600_OUTPUT_CFG1,
+ CS2600_FSYNC_DUTY_CYCLE_MASK,
+ FIELD_PREP(CS2600_FSYNC_DUTY_CYCLE_MASK,
+ ffs(val)));
+}
+
+static const char * const cs2600_aux1_output_names[] = {
+ "ref_clk_in",
+ "clk_in",
+ "freq_unlock",
+ "phase_unlock",
+ "clkin_missing",
+};
+
+static const u8 cs2600_aux1_output_modes[] = {
+ CS2600_AUX1_OUT_REF_CLK_IN_VAL,
+ CS2600_AUX1_OUT_CLK_IN_VAL,
+ CS2600_AUX1_OUT_FREQ_UNLOCK_VAL,
+ CS2600_AUX1_OUT_PHASE_UNLOCK_VAL,
+ CS2600_AUX1_OUT_NO_CLKIN_VAL,
+};
+
+static int cs2600_set_aux1_output(struct cs2600 *cs2600)
+{
+ const char *source;
+ unsigned int val;
+ int i, ret;
+
+ if (device_property_read_string(cs2600->dev, "cirrus,aux1-output-source", &source))
+ return 0;
+
+ i = match_string(cs2600_aux1_output_names, ARRAY_SIZE(cs2600_aux1_output_names), source);
+ if (i < 0)
+ return dev_err_probe(cs2600->dev, -EINVAL, "Invalid aux1 output %s\n", source);
+
+ static_assert(ARRAY_SIZE(cs2600_aux1_output_modes) == ARRAY_SIZE(cs2600_aux1_output_names));
+ val = cs2600_aux1_output_modes[i];
+
+ ret = regmap_update_bits(cs2600->regmap, CS2600_OUTPUT_CFG2,
+ CS2600_AUX1OUT_SEL, FIELD_PREP(CS2600_AUX1OUT_SEL, val));
+ if (ret)
+ return ret;
+
+ return regmap_clear_bits(cs2600->regmap, CS2600_PLL_CFG1, CS2600_AUX1_OUT_DIS);
+}
+
+static int cs2600_set_refclk_source(struct cs2600 *cs2600)
+{
+ unsigned int src_field_val;
+
+ if (device_property_present(cs2600->dev, "cirrus,internal-oscillator")) {
+ if (cs2600->refclk_rate != CS2600_INTERNAL_OSC_RATE) {
+ return dev_err_probe(cs2600->dev, -EINVAL,
+ "Illegal rate for internal oscillator\n");
+ }
+ src_field_val = CS2600_SYSCLK_SRC_OSC;
+ } else {
+ src_field_val = CS2600_SYSCLK_SRC_REFCLK;
+ }
+
+ return regmap_update_bits(cs2600->regmap, CS2600_PLL_CFG3,
+ CS2600_SYSCLK_SRC_MASK,
+ FIELD_PREP(CS2600_SYSCLK_SRC_MASK, src_field_val));
+}
+
+static int cs2600_parse_properties(struct cs2600 *cs2600)
+{
+ int ret;
+
+ guard(cs2600_unfreeze)(cs2600);
+
+ ret = cs2600_set_mode(cs2600);
+ if (ret)
+ return ret;
+
+ ret = cs2600_set_output_inverts(cs2600);
+ if (ret)
+ return ret;
+
+ ret = cs2600_set_fsync_duty(cs2600);
+ if (ret)
+ return ret;
+
+ ret = cs2600_set_aux1_output(cs2600);
+ if (ret)
+ return ret;
+
+ return cs2600_set_refclk_source(cs2600);
+}
+
+static int cs2600_check_device_id(struct cs2600 *cs2600)
+{
+ struct device *dev = cs2600->dev;
+ unsigned int dev_id, rev;
+ int ret;
+
+ ret = regmap_read(cs2600->regmap, CS2600_DEVICE_ID1, &dev_id);
+ if (ret)
+ return dev_err_probe(dev, ret, "Can't read device ID\n");
+
+ if (dev_id != CS2600_DEVICE_ID_VALUE)
+ return dev_err_probe(dev, -ENODEV, "Invalid device id %#x\n",
+ dev_id);
+
+ ret = regmap_read(cs2600->regmap, CS2600_DEVICE_ID2, &rev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Can't read device revision\n");
+
+ dev_info(dev, "ID %x Rev %X", dev_id, rev);
+
+ return 0;
+}
+
+static int cs2600_i2c_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct cs2600 *cs2600;
+ int ret;
+
+ cs2600 = devm_kzalloc(dev, sizeof(*cs2600), GFP_KERNEL);
+ if (!cs2600)
+ return -ENOMEM;
+
+ ret = devm_regulator_get_enable(dev, "vdd");
+ if (ret)
+ return dev_err_probe(dev, ret, "Error with vdd supply\n");
+
+ cs2600->dev = dev;
+ i2c_set_clientdata(client, cs2600);
+
+ cs2600->regmap = devm_regmap_init_i2c(client, &cs2600_regmap_config);
+ if (IS_ERR(cs2600->regmap))
+ return dev_err_probe(dev, PTR_ERR(cs2600->regmap),
+ "Regmap not created\n");
+
+ /* Required to wait at least 20ms after vdd is enabled */
+ usleep_range(20000, 21000);
+ ret = cs2600_check_device_id(cs2600);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(cs2600->regmap, CS2600_SW_RESET, CS2600_SW_RST_VAL);
+ if (ret)
+ return ret;
+
+ /* Required to wait at least 5ms after software reset */
+ usleep_range(5000, 6000);
+ ret = cs2600_clk_get(cs2600);
+ if (ret)
+ return dev_err_probe(dev, ret, "Invalid parent clocks\n");
+
+ /* Set outputs to HiZ */
+ scoped_guard(cs2600_unfreeze, cs2600) {
+ ret = regmap_set_bits(cs2600->regmap, CS2600_PLL_CFG1,
+ CS2600_CLK_OUT_DIS | CS2600_AUX1_OUT_DIS);
+ if (ret)
+ return ret;
+
+ ret = regmap_set_bits(cs2600->regmap, CS2600_OUTPUT_CFG1,
+ CS2600_BCLK_OUT_DIS | CS2600_FSYNC_OUT_DIS);
+ if (ret)
+ return ret;
+ }
+
+ ret = cs2600_parse_properties(cs2600);
+ if (ret)
+ return dev_err_probe(dev, ret, "Cannot parse dt params\n");
+
+ ret = cs2600_ref_clk_set_divider(cs2600, cs2600->refclk_rate);
+ if (ret) {
+ return dev_err_probe(dev, ret, "Invalid ref_clk %lu Hz\n",
+ cs2600->refclk_rate);
+ }
+
+ ret = cs2600_clk_register(cs2600);
+ if (ret)
+ return dev_err_probe(dev, ret, "Cannot register clocks\n");
+
+ return 0;
+}
+
+static const struct of_device_id cs2600_of_match[] = {
+ { .compatible = "cirrus,cs2600", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, cs2600_of_match);
+
+static const struct i2c_device_id cs2600_id[] = {
+ { "cs2600", },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, cs2600_id);
+
+static struct i2c_driver cs2600_driver = {
+ .driver = {
+ .name = "cs2600",
+ .of_match_table = cs2600_of_match,
+ },
+ .probe = cs2600_i2c_probe,
+ .id_table = cs2600_id,
+};
+
+module_i2c_driver(cs2600_driver);
+
+MODULE_DESCRIPTION("CS2600 clock driver");
+MODULE_AUTHOR("Paul Handrigan <paulha@opensource.cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/cirrus/clk-cs2600.h b/drivers/clk/cirrus/clk-cs2600.h
new file mode 100644
index 000000000000..53570e065620
--- /dev/null
+++ b/drivers/clk/cirrus/clk-cs2600.h
@@ -0,0 +1,163 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * CS2600 clock driver
+ *
+ * Copyright (C) 2023-2026 Cirrus Logic, Inc. and
+ * Cirrus Logic International Semiconductor Ltd.
+ */
+
+#ifndef _CS2600_H
+#define _CS2600_H
+
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+
+/* Registers */
+
+#define CS2600_PLL_CFG1 0x0002
+#define CS2600_PLL_CFG2 0x0004
+
+#define CS2600_RATIO1_1 0x0006
+#define CS2600_RATIO1_2 0x0008
+#define CS2600_RATIO2_1 0x000A
+#define CS2600_RATIO2_2 0x000C
+
+#define CS2600_PLL_CFG3 0x0016
+#define CS2600_SW_RESET 0x0058
+#define CS2600_OUTPUT_CFG1 0x0100
+#define CS2600_OUTPUT_CFG2 0x0102
+#define CS2600_PHASE_ALIGNMENT_CFG1 0x0108
+
+#define CS2600_DEVICE_ID1 0x0110
+#define CS2600_DEVICE_ID2 0x0112
+
+#define CS2600_UNLOCK_INDICATORS 0x0114
+#define CS2600_ERROR_STS 0x0116
+
+#define CS2600_MAX_REGISTER CS2600_ERROR_STS
+
+/* Register Fields */
+
+/* PLL_CFG1 */
+#define CS2600_S_RATIO_SEL_MASK GENMASK(12, 11)
+#define CS2600_PLL_EN1 BIT(8)
+#define CS2600_AUX1_OUT_DIS BIT(1)
+#define CS2600_CLK_OUT_DIS BIT(0)
+
+/* PLL_CFG2 */
+#define CS2600_FREEZE_EN BIT(11)
+#define CS2600_PLL_EN2 BIT(8)
+#define CS2600_M_RATIO_SEL_MASK GENMASK(2, 1)
+#define CS2600_PLL_MODE_SEL BIT(0)
+
+/* PLL_CFG3 */
+#define CS2600_OUT_GATE BIT(12)
+#define CS2600_RATIO_CFG BIT(11)
+#define CS2600_REF_CLK_IN_DIV_MASK GENMASK(4, 3)
+#define CS2600_SYSCLK_SRC_MASK GENMASK(2, 1)
+
+#define CS2600_SYSCLK_SRC_REFCLK 1
+#define CS2600_SYSCLK_SRC_OSC 2
+
+/* SW_RESET */
+#define CS2600_SW_RST_VAL 0x5A
+
+/* OUTPUT_CFG1 */
+#define CS2600_BCLK_INV BIT(7)
+#define CS2600_BCLK_OUT_DIS BIT(6)
+#define CS2600_FSYNC_DUTY_CYCLE_MASK GENMASK(4, 2)
+#define CS2600_FSYNC_INV BIT(1)
+#define CS2600_FSYNC_OUT_DIS BIT(0)
+#define CS2600_BCLK_DIV_MASK GENMASK(15, 12)
+#define CS2600_FSYNC_DIV_MASK GENMASK(11, 8)
+
+/* PHASE_ALIGNMENT_CFG1 */
+#define CS2600_PHASE_ALIGN_EN BIT(15)
+#define CS2600_PHASE_ALIGN_STB_EN BIT(7)
+
+/* OUTPUT_CFG2 */
+#define CS2600_AUX1OUT_SEL GENMASK(12, 10)
+#define CS2600_AUX2OUT_SEL GENMASK(9, 8)
+
+/* UNLOCK_INDICATORS */
+#define CS2600_P_UNLOCK_STICKY BIT(3)
+#define CS2600_P_UNLOCK BIT(2)
+#define CS2600_F_UNLOCK_STICKY BIT(1)
+#define CS2600_F_UNLOCK BIT(0)
+
+/* ERROR_STS */
+#define CS2600_ERR_DEV_DEFECT BIT(7)
+#define CS2600_ERR_OTP_CORRUPT BIT(6)
+#define CS2600_ERR_REG_CFG BIT(5)
+#define CS2600_ERR_PLL_DISABLED BIT(4)
+#define CS2600_ERR_HW_CFG BIT(3)
+#define CS2600_ERR_REFCLK_MISSING BIT(2)
+#define CS2600_ERR_CLKIN_UNSTABLE BIT(1)
+#define CS2600_ERR_CLKIN_MISSING BIT(0)
+
+#define CS2600_LOCK_ATTEMPTS_MAX 100
+#define CS2600_CLEAR_INDICATORS 0xA
+
+#define CS2600_REFCLK_INPUT 0
+#define CS2600_CLK_IN_INPUT 1
+
+#define CS2600_12_20_SHIFT 20
+#define CS2600_20_12_SHIFT 12
+#define CS2600_12_20_PPM 1
+#define CS2600_20_12_PPM 224
+#define CS2600_12_20_RATIO_MAX 0xffffffffUL
+#define CS2600_20_12_RATIO_MAX 0xffffffffUL
+
+#define CS2600_CLK_OUT_MIN 6000000
+#define CS2600_CLK_OUT_MAX 75000000
+#define CS2600_BCLK_OUT_MIN (CS2600_CLK_OUT_MIN / 48)
+#define CS2600_BCLK_OUT_MAX CS2600_CLK_OUT_MAX
+#define CS2600_FSYNC_OUT_MIN (CS2600_CLK_OUT_MIN / 1536)
+#define CS2600_FSYNC_OUT_MAX (CS2600_CLK_OUT_MAX / 16)
+
+#define CS2600_FSYNC_MIN_CYCLES 1
+#define CS2600_FSYNC_MAX_CYCLES 32
+
+#define CS2600_AUX1_OUT_REF_CLK_IN_VAL 0x0
+#define CS2600_AUX1_OUT_CLK_IN_VAL 0x1
+#define CS2600_AUX1_OUT_FREQ_UNLOCK_VAL 0x3
+#define CS2600_AUX1_OUT_PHASE_UNLOCK_VAL 0x4
+#define CS2600_AUX1_OUT_NO_CLKIN_VAL 0x7
+
+#define CS2600_OUT_CLK_MAX 4
+#define CS2600_INTERNAL_OSC_RATE 12000000
+
+#define CS2600_DEVICE_ID_VALUE 0x2600
+
+struct cs2600;
+
+struct cs2600_clk_hw {
+ struct clk_hw hw;
+ struct clk_init_data init;
+ struct cs2600 *priv;
+};
+
+enum cs2600_mode {
+ CS2600_MANUAL_MODE = 0,
+ CS2600_SMART_MODE = 1,
+ CS2600_SMART_CLKIN_ONLY_MODE = 2,
+};
+
+/* CS2600 private data */
+struct cs2600 {
+ struct device *dev;
+ struct regmap *regmap;
+ struct cs2600_clk_hw hw[CS2600_OUT_CLK_MAX];
+
+ struct clk *clk_in;
+ struct clk *ref_clk;
+
+ enum cs2600_mode mode;
+ unsigned long refclk_rate;
+ unsigned long pll_target_rate;
+ unsigned long pll_parent_rate;
+};
+
+#endif
--
2.47.3
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v6 3/3] clk: cs2600: Add KUnit test for CS2600 driver
2026-06-30 15:55 [PATCH v6 0/3] Cirrus Logic CS2600 clock device Richard Fitzgerald
2026-06-30 15:55 ` [PATCH v6 1/3] dt-bindings: clock: cs2600: Add support for the CS2600 Richard Fitzgerald
2026-06-30 15:55 ` [PATCH v6 2/3] clk: cs2600: Add Fractional-N clock driver Richard Fitzgerald
@ 2026-06-30 15:55 ` Richard Fitzgerald
2026-06-30 16:14 ` sashiko-bot
2 siblings, 1 reply; 10+ messages in thread
From: Richard Fitzgerald @ 2026-06-30 15:55 UTC (permalink / raw)
To: mturquette, sboyd, bmasney, robh, krzk+dt, conor+dt
Cc: linux-clk, devicetree, linux-kernel, patches
Add a KUnit test for the CS2600 clock driver.
This test uses a mock I2C host driver and mock regmap to partially
emulate the hardware. The regmap is permanently in cache-only mode so
that it will act as an emulation of a set of device registers. Some
special cases are implemented to emulate behavior of certain registers:
- Some register bits can only be changed when the FREEZE_EN bit is 0.
- UNLOCK status bits are 1 if the PLL is disabled or not locked.
The bulk of the testing is to confirm that the frequency ratio and PLL
configuration register fields are set correctly. The PLL configuration
and ratio precision depend on several factors:
- The frequency reference source (REF_CLK_IN or CLK_IN).
- The magnitude of frequency difference between input and output clocks.
- Whether the CS2600 is operating in smart mode.
The test uses hardcoded tables of the expected ratio register settings
for pairs of IN:OUT clock frequencies in various modes. The values in
these tables were derived independently from the calculation code in
the clk-cs2600.c driver. The purpose of this is to avoid false test
passes caused by the test code having the same calculation bug as the
code it is testing. Using an independent source of ratios reduces this
risk. These tables do not attempt to test every possible ratio, but
focus on (a) typical frequencies, and (b) edge cases where rounding is
expected.
Devicetree overlay files are used to configure properties for a variety
of possible hardware configurations. There are test cases to validate
that the properties are applied correctly.
Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
---
New in V6 series.
drivers/clk/cirrus/Kconfig | 13 +
drivers/clk/cirrus/Makefile | 24 +
drivers/clk/cirrus/clk-cs2600-kunit.c | 2495 +++++++++++++++++
drivers/clk/cirrus/clk-cs2600-kunit.h | 18 +
drivers/clk/cirrus/kunit_clk_cs2600.dtsi | 40 +
.../clk/cirrus/kunit_clk_cs2600_assigned.dtso | 27 +
.../kunit_clk_cs2600_aux1_out_clk_in.dtso | 17 +
...it_clk_cs2600_aux1_out_clk_in_missing.dtso | 17 +
...kunit_clk_cs2600_aux1_out_freq_unlock.dtso | 17 +
...unit_clk_cs2600_aux1_out_phase_unlock.dtso | 17 +
.../kunit_clk_cs2600_aux1_out_ref_clk_in.dtso | 17 +
.../cirrus/kunit_clk_cs2600_bclk_invert.dtso | 17 +
.../cirrus/kunit_clk_cs2600_fsync_duty_1.dtso | 17 +
.../cirrus/kunit_clk_cs2600_fsync_duty_2.dtso | 17 +
.../kunit_clk_cs2600_fsync_duty_32.dtso | 17 +
.../cirrus/kunit_clk_cs2600_fsync_invert.dtso | 17 +
.../kunit_clk_cs2600_manual_intosc_clkin.dtso | 17 +
.../kunit_clk_cs2600_manual_intosc_only.dtso | 17 +
.../kunit_clk_cs2600_manual_refclk_clkin.dtso | 16 +
.../kunit_clk_cs2600_manual_refclk_only.dtso | 16 +
.../kunit_clk_cs2600_name_output_clocks.dtso | 23 +
...it_clk_cs2600_smart_clkin_only_intosc.dtso | 19 +
...it_clk_cs2600_smart_clkin_only_refclk.dtso | 18 +
.../cirrus/kunit_clk_cs2600_smart_intosc.dtso | 18 +
.../cirrus/kunit_clk_cs2600_smart_refclk.dtso | 17 +
25 files changed, 2948 insertions(+)
create mode 100644 drivers/clk/cirrus/clk-cs2600-kunit.c
create mode 100644 drivers/clk/cirrus/clk-cs2600-kunit.h
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600.dtsi
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_assigned.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_clk_in.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_clk_in_missing.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_freq_unlock.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_phase_unlock.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_ref_clk_in.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_bclk_invert.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_fsync_duty_1.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_fsync_duty_2.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_fsync_duty_32.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_fsync_invert.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_manual_intosc_clkin.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_manual_intosc_only.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_manual_refclk_clkin.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_manual_refclk_only.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_name_output_clocks.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_smart_clkin_only_intosc.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_smart_clkin_only_refclk.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_smart_intosc.dtso
create mode 100644 drivers/clk/cirrus/kunit_clk_cs2600_smart_refclk.dtso
diff --git a/drivers/clk/cirrus/Kconfig b/drivers/clk/cirrus/Kconfig
index 5674d6554f96..a6807cab4b94 100644
--- a/drivers/clk/cirrus/Kconfig
+++ b/drivers/clk/cirrus/Kconfig
@@ -8,3 +8,16 @@ config COMMON_CLK_CS2600
help
If you say yes here you get support for the CS2600 clock synthesizer
and multiplier.
+
+config COMMON_CLK_CS2600_KUNIT_TEST
+ tristate "KUnit test for CS2600 clock driver" if !KUNIT_ALL_TESTS
+ depends on COMMON_CLK_CS2600
+ depends on KUNIT
+ depends on REGULATOR
+ depends on REGULATOR_FIXED_VOLTAGE
+ default KUNIT_ALL_TESTS
+ help
+ Build the KUnit tests for the Cirrus Logic CS2600
+ clock driver.
+
+ If unsure say N.
diff --git a/drivers/clk/cirrus/Makefile b/drivers/clk/cirrus/Makefile
index 182612dbb96a..be59a601d936 100644
--- a/drivers/clk/cirrus/Makefile
+++ b/drivers/clk/cirrus/Makefile
@@ -1,3 +1,27 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_COMMON_CLK_CS2600) += clk-cs2600.o
+
+clk-cs2600_kunit-y := clk-cs2600-kunit.o \
+ kunit_clk_cs2600_assigned.dtbo.o \
+ kunit_clk_cs2600_aux1_out_clk_in.dtbo.o \
+ kunit_clk_cs2600_aux1_out_clk_in_missing.dtbo.o \
+ kunit_clk_cs2600_aux1_out_freq_unlock.dtbo.o \
+ kunit_clk_cs2600_aux1_out_phase_unlock.dtbo.o \
+ kunit_clk_cs2600_aux1_out_ref_clk_in.dtbo.o \
+ kunit_clk_cs2600_bclk_invert.dtbo.o \
+ kunit_clk_cs2600_fsync_duty_1.dtbo.o \
+ kunit_clk_cs2600_fsync_duty_2.dtbo.o \
+ kunit_clk_cs2600_fsync_duty_32.dtbo.o \
+ kunit_clk_cs2600_fsync_invert.dtbo.o \
+ kunit_clk_cs2600_manual_intosc_clkin.dtbo.o \
+ kunit_clk_cs2600_manual_intosc_only.dtbo.o \
+ kunit_clk_cs2600_manual_refclk_clkin.dtbo.o \
+ kunit_clk_cs2600_manual_refclk_only.dtbo.o \
+ kunit_clk_cs2600_name_output_clocks.dtbo.o \
+ kunit_clk_cs2600_smart_clkin_only_intosc.dtbo.o \
+ kunit_clk_cs2600_smart_clkin_only_refclk.dtbo.o \
+ kunit_clk_cs2600_smart_intosc.dtbo.o \
+ kunit_clk_cs2600_smart_refclk.dtbo.o
+
+obj-$(CONFIG_COMMON_CLK_CS2600_KUNIT_TEST) += clk-cs2600_kunit.o
diff --git a/drivers/clk/cirrus/clk-cs2600-kunit.c b/drivers/clk/cirrus/clk-cs2600-kunit.c
new file mode 100644
index 000000000000..9631801ac553
--- /dev/null
+++ b/drivers/clk/cirrus/clk-cs2600-kunit.c
@@ -0,0 +1,2495 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// KUnit test for the Cirrus Logic CS2600 clock driver.
+//
+// Copyright (C) 2026 Cirrus Logic, Inc. and
+// Cirrus Logic International Semiconductor Ltd.
+
+#include <dt-bindings/clock/cirrus,cs2600-clock.h>
+#include <kunit/clk.h>
+#include <kunit/of.h>
+#include <kunit/platform_device.h>
+#include <kunit/resource.h>
+#include <kunit/test.h>
+#include <linux/bitfield.h>
+#include <linux/clk-provider.h>
+#include <linux/completion.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include "clk-cs2600.h"
+#include "clk-cs2600-kunit.h"
+
+/* Used for non-parameterized tests so that there is a valid value */
+#define CS2600_KUNIT_DEFAULT_REFCLK_RATE 12000000
+
+KUNIT_DEFINE_ACTION_WRAPPER(clk_disable_unprepare_wrapper,
+ clk_disable_unprepare, struct clk *);
+
+struct cs2600_kunit_priv {
+ struct kunit *test;
+
+ struct platform_driver i2c_pdrv;
+ struct i2c_adapter adapter;
+ struct regmap *regmap;
+ unsigned int force_unlock_status;
+
+ struct platform_driver refclk_pdrv;
+ struct clk_hw *refclk_hw;
+ unsigned int refclk_rate;
+
+ struct platform_driver clkin_pdrv;
+ struct clk_hw *clkin_hw;
+ unsigned int clkin_rate;
+
+ struct platform_driver consumer_pdrv;
+ struct clk *pllout;
+ struct clk *clkout;
+ struct clk *bclk;
+ struct clk *fsync;
+ struct completion consumers_completion;
+};
+
+struct cs2600_kunit_ratio_config {
+ unsigned int clk_in;
+ unsigned int clk_out;
+ u32 r;
+ bool hires;
+};
+
+struct cs2600_kunit_div_config {
+ unsigned int pll_out;
+ unsigned int bf_out;
+ u32 r;
+};
+
+struct cs2600_kunit_params {
+ unsigned int ref_clk;
+ unsigned int clk_in;
+ const struct cs2600_kunit_ratio_config *s_ratios;
+ const struct cs2600_kunit_ratio_config *m_ratios;
+};
+
+/*
+ * Expected ratios are hardcoded from independent known-good data.
+ * This avoids the possibility that calculating them from an algorithm has the
+ * same mistake in both the driver and test code, and so incorrectly marks a
+ * test as passed.
+ *
+ * These are deliberately partially unsorted to help detect bugs.
+ *
+ * Tables for source clocks < 6MHz are for multiplier mode.
+ * Tables for source clocks between 6MHz and 30MHz are for either mode.
+ * Tables for source clocks > 30MHz are for synthesizer mode.
+ * The minimum clk_out is 6.0 MHz and this is only possible for certain
+ * input clocks. Where it is impossible the .r value is given as 0.
+ */
+static const struct cs2600_kunit_ratio_config cs2600_kunit_50_ratios[] = {
+ { .clk_out = 12000000, .r = 0x3a980000, .hires = false },
+ { .clk_out = 8000000, .r = 0x27100000, .hires = false },
+ { .clk_out = 6000000, .r = 0x1d4c0000, .hires = false },
+ { .clk_out = 24576000, .r = 0x78000000, .hires = false },
+ { .clk_out = 12000001, .r = 0x3a980051, .hires = false },
+ { .clk_out = 12000011, .r = 0x3a980385, .hires = false },
+ { .clk_out = 12000012, .r = 0x3a9803d7, .hires = false },
+ { .clk_out = 11999999, .r = 0x3a97ffae, .hires = false },
+ { .clk_out = 11999988, .r = 0x3a97fc28, .hires = false },
+ { .clk_out = 11999987, .r = 0x3a97fbd7, .hires = false },
+ { .clk_out = 8000004, .r = 0x27100147, .hires = false },
+ { .clk_out = 6144000, .r = 0x1e000000, .hires = false },
+ { .clk_out = 6250000, .r = 0x1e848000, .hires = false },
+ { .clk_out = 6400000, .r = 0x1f400000, .hires = false },
+ { .clk_out = 8192000, .r = 0x28000000, .hires = false },
+ { .clk_out = 9600000, .r = 0x2ee00000, .hires = false },
+ { .clk_out = 12288000, .r = 0x3c000000, .hires = false },
+ { .clk_out = 13500000, .r = 0x41eb0000, .hires = false },
+ { .clk_out = 19200000, .r = 0x5dc00000, .hires = false },
+ { .clk_out = 22579200, .r = 0x6e400000, .hires = false },
+ { /* terminator */}
+};
+
+static const struct cs2600_kunit_ratio_config cs2600_kunit_2k_ratios[] = {
+ { .clk_out = 12000000, .r = 0x01770000, .hires = false },
+ { .clk_out = 8000000, .r = 0xfa000000, .hires = true },
+ { .clk_out = 6000000, .r = 0xbb800000, .hires = true },
+ { .clk_out = 24576000, .r = 0x03000000, .hires = false },
+ { .clk_out = 12000001, .r = 0x01770002, .hires = false },
+ { .clk_out = 12000011, .r = 0x01770016, .hires = false },
+ { .clk_out = 12000012, .r = 0x01770018, .hires = false },
+ { .clk_out = 11999999, .r = 0x0176fffd, .hires = false },
+ { .clk_out = 11999988, .r = 0x0176ffe7, .hires = false },
+ { .clk_out = 11999987, .r = 0x0176ffe5, .hires = false },
+ { .clk_out = 8000004, .r = 0xfa000831, .hires = true },
+ { .clk_out = 6144000, .r = 0xc0000000, .hires = true },
+ { .clk_out = 6250000, .r = 0xc3500000, .hires = true },
+ { .clk_out = 6400000, .r = 0xc8000000, .hires = true },
+ { .clk_out = 8192000, .r = 0x01000000, .hires = false },
+ { .clk_out = 9600000, .r = 0x012c0000, .hires = false },
+ { .clk_out = 12288000, .r = 0x01800000, .hires = false },
+ { .clk_out = 13500000, .r = 0x01a5e000, .hires = false },
+ { .clk_out = 19200000, .r = 0x02580000, .hires = false },
+ { .clk_out = 22579200, .r = 0x02c19999, .hires = false },
+ { /* terminator */}
+};
+
+static const struct cs2600_kunit_ratio_config cs2600_kunit_48k_ratios[] = {
+ { .clk_out = 12000000, .r = 0x0fa00000, .hires = true },
+ { .clk_out = 8000000, .r = 0x0a6aaaaa, .hires = true },
+ { .clk_out = 6000000, .r = 0x07d00000, .hires = true },
+ { .clk_out = 24576000, .r = 0x20000000, .hires = true },
+ { .clk_out = 12000001, .r = 0x0fa00015, .hires = true },
+ { .clk_out = 12000011, .r = 0x0fa000f0, .hires = true },
+ { .clk_out = 12000012, .r = 0x0fa00106, .hires = true },
+ { .clk_out = 11999999, .r = 0x0f9fffea, .hires = true },
+ { .clk_out = 11999988, .r = 0x0f9ffef9, .hires = true },
+ { .clk_out = 11999987, .r = 0x0f9ffee4, .hires = true },
+ { .clk_out = 8000004, .r = 0x0a6aab02, .hires = true },
+ { .clk_out = 6144000, .r = 0x08000000, .hires = true },
+ { .clk_out = 6250000, .r = 0x08235555, .hires = true },
+ { .clk_out = 6400000, .r = 0x08555555, .hires = true },
+ { .clk_out = 8192000, .r = 0x0aaaaaaa, .hires = true },
+ { .clk_out = 9600000, .r = 0x0c800000, .hires = true },
+ { .clk_out = 12288000, .r = 0x10000000, .hires = true },
+ { .clk_out = 13500000, .r = 0x11940000, .hires = true },
+ { .clk_out = 19200000, .r = 0x19000000, .hires = true },
+ { .clk_out = 22579200, .r = 0x1d666666, .hires = true },
+ { /* terminator */}
+};
+
+static const struct cs2600_kunit_ratio_config cs2600_kunit_1_4112M_ratios[] = {
+ { .clk_out = 12000000, .r = 0x0880dee, .hires = true },
+ { .clk_out = 8000000, .r = 0x05ab3f4, .hires = true },
+ { .clk_out = 6000000, .r = 0, .hires = true },
+ { .clk_out = 24576000, .r = 0x116a3b3, .hires = true },
+ { .clk_out = 12000001, .r = 0x0880def, .hires = true },
+ { .clk_out = 12000011, .r = 0x0880df6, .hires = true },
+ { .clk_out = 12000012, .r = 0x0880df7, .hires = true },
+ { .clk_out = 11999999, .r = 0x0880ded, .hires = true },
+ { .clk_out = 11999988, .r = 0x0880de5, .hires = true },
+ { .clk_out = 11999987, .r = 0x0880de4, .hires = true },
+ { .clk_out = 8000004, .r = 0x05ab3f7, .hires = true },
+ { .clk_out = 6144000, .r = 0x045a8ec, .hires = true },
+ { .clk_out = 6250000, .r = 0x046dc96, .hires = true },
+ { .clk_out = 6400000, .r = 0x0488ff6, .hires = true },
+ { .clk_out = 8192000, .r = 0x05ce13b, .hires = true },
+ { .clk_out = 9600000, .r = 0x06cd7f2, .hires = true },
+ { .clk_out = 12288000, .r = 0x08b51d9, .hires = true },
+ { .clk_out = 13500000, .r = 0x0990fac, .hires = true },
+ { .clk_out = 19200000, .r = 0x0d9afe4, .hires = true },
+ { .clk_out = 22579200, .r = 0x1000000, .hires = true },
+ { /* terminator */}
+};
+
+static const struct cs2600_kunit_ratio_config cs2600_kunit_8M_ratios[] = {
+ { .clk_out = 12000000, .r = 0x00180000, .hires = true },
+ { .clk_out = 8000000, .r = 0x00100000, .hires = true },
+ { .clk_out = 6000000, .r = 0x000c0000, .hires = true },
+ { .clk_out = 24576000, .r = 0x003126e9, .hires = true },
+ { .clk_out = 12000001, .r = 0x00180000, .hires = true },
+ { .clk_out = 12000011, .r = 0x00180001, .hires = true },
+ { .clk_out = 12000012, .r = 0x00180001, .hires = true },
+ { .clk_out = 11999999, .r = 0x0017ffff, .hires = true },
+ { .clk_out = 11999988, .r = 0x0017fffe, .hires = true },
+ { .clk_out = 11999987, .r = 0x0017fffe, .hires = true },
+ { .clk_out = 8000004, .r = 0x00100000, .hires = true },
+ { .clk_out = 6144000, .r = 0x000c49ba, .hires = true },
+ { .clk_out = 6250000, .r = 0x000c8000, .hires = true },
+ { .clk_out = 6400000, .r = 0x000ccccc, .hires = true },
+ { .clk_out = 8192000, .r = 0x0010624d, .hires = true },
+ { .clk_out = 9600000, .r = 0x00133333, .hires = true },
+ { .clk_out = 12288000, .r = 0x00189374, .hires = true },
+ { .clk_out = 13500000, .r = 0x001b0000, .hires = true },
+ { .clk_out = 19200000, .r = 0x00266666, .hires = true },
+ { .clk_out = 22579200, .r = 0x002d288c, .hires = true },
+ { .clk_out = 75000000, .r = 0x00960000, .hires = true },
+ { .clk_out = 74999999, .r = 0x0095ffff, .hires = true },
+ { .clk_out = 32000000, .r = 0x00400000, .hires = true },
+ { .clk_out = 49152000, .r = 0x00624dd2, .hires = true },
+ { .clk_out = 45158400, .r = 0x005a5119, .hires = true },
+ { /* terminator */}
+};
+
+static const struct cs2600_kunit_ratio_config cs2600_kunit_12M_ratios[] = {
+ { .clk_out = 12000000, .r = 0x00100000, .hires = true },
+ { .clk_out = 8000000, .r = 0x000aaaaa, .hires = true },
+ { .clk_out = 6000000, .r = 0x00080000, .hires = true },
+ { .clk_out = 24576000, .r = 0x0020c49b, .hires = true },
+ { .clk_out = 12000001, .r = 0x00100000, .hires = true },
+ { .clk_out = 12000011, .r = 0x00100000, .hires = true },
+ { .clk_out = 12000012, .r = 0x00100001, .hires = true },
+ { .clk_out = 11999999, .r = 0x000fffff, .hires = true },
+ { .clk_out = 11999988, .r = 0x000ffffe, .hires = true },
+ { .clk_out = 11999987, .r = 0x000ffffe, .hires = true },
+ { .clk_out = 8000004, .r = 0x000aaaab, .hires = true },
+ { .clk_out = 6144000, .r = 0x00083126, .hires = true },
+ { .clk_out = 6250000, .r = 0x00085555, .hires = true },
+ { .clk_out = 6400000, .r = 0x00088888, .hires = true },
+ { .clk_out = 8192000, .r = 0x000aec33, .hires = true },
+ { .clk_out = 9600000, .r = 0x000ccccc, .hires = true },
+ { .clk_out = 12288000, .r = 0x0010624d, .hires = true },
+ { .clk_out = 13500000, .r = 0x00120000, .hires = true },
+ { .clk_out = 19200000, .r = 0x00199999, .hires = true },
+ { .clk_out = 22579200, .r = 0x001e1b08, .hires = true },
+ { .clk_out = 75000000, .r = 0x00640000, .hires = true },
+ { .clk_out = 74999999, .r = 0x0063ffff, .hires = true },
+ { .clk_out = 32000000, .r = 0x002aaaaa, .hires = true },
+ { .clk_out = 49152000, .r = 0x00418937, .hires = true },
+ { .clk_out = 45158400, .r = 0x003c3611, .hires = true },
+ { /* terminator */}
+};
+
+static const struct cs2600_kunit_ratio_config cs2600_kunit_16M_ratios[] = {
+ { .clk_out = 12000000, .r = 0x000c0000, .hires = true },
+ { .clk_out = 8000000, .r = 0x00080000, .hires = true },
+ { .clk_out = 6000000, .r = 0x00060000, .hires = true },
+ { .clk_out = 24576000, .r = 0x00189374, .hires = true },
+ { .clk_out = 12000001, .r = 0x000c0000, .hires = true },
+ { .clk_out = 12000011, .r = 0x000c0000, .hires = true },
+ { .clk_out = 12000012, .r = 0x000c0000, .hires = true },
+ { .clk_out = 11999999, .r = 0x000bffff, .hires = true },
+ { .clk_out = 11999988, .r = 0x000bffff, .hires = true },
+ { .clk_out = 11999987, .r = 0x000bffff, .hires = true },
+ { .clk_out = 8000004, .r = 0x00080000, .hires = true },
+ { .clk_out = 6144000, .r = 0x000624dd, .hires = true },
+ { .clk_out = 6250000, .r = 0x00064000, .hires = true },
+ { .clk_out = 6400000, .r = 0x00066666, .hires = true },
+ { .clk_out = 8192000, .r = 0x00083126, .hires = true },
+ { .clk_out = 9600000, .r = 0x00099999, .hires = true },
+ { .clk_out = 12288000, .r = 0x000c49ba, .hires = true },
+ { .clk_out = 13500000, .r = 0x000d8000, .hires = true },
+ { .clk_out = 19200000, .r = 0x00133333, .hires = true },
+ { .clk_out = 22579200, .r = 0x00169446, .hires = true },
+ { .clk_out = 75000000, .r = 0x004b0000, .hires = true },
+ { .clk_out = 74999999, .r = 0x004affff, .hires = true },
+ { .clk_out = 32000000, .r = 0x00200000, .hires = true },
+ { .clk_out = 49152000, .r = 0x003126e9, .hires = true },
+ { .clk_out = 45158400, .r = 0x002d288c, .hires = true },
+ { /* terminator */}
+};
+
+static const struct cs2600_kunit_ratio_config cs2600_kunit_19_2M_ratios[] = {
+ { .clk_out = 12000000, .r = 0x000a0000, .hires = true },
+ { .clk_out = 8000000, .r = 0x0006aaaa, .hires = true },
+ { .clk_out = 6000000, .r = 0x00050000, .hires = true },
+ { .clk_out = 24576000, .r = 0x00147ae1, .hires = true },
+ { .clk_out = 12000001, .r = 0x000a0000, .hires = true },
+ { .clk_out = 12000011, .r = 0x000a0000, .hires = true },
+ { .clk_out = 12000012, .r = 0x000a0000, .hires = true },
+ { .clk_out = 11999999, .r = 0x0009ffff, .hires = true },
+ { .clk_out = 11999988, .r = 0x0009ffff, .hires = true },
+ { .clk_out = 11999987, .r = 0x0009ffff, .hires = true },
+ { .clk_out = 8000004, .r = 0x0006aaaa, .hires = true },
+ { .clk_out = 6144000, .r = 0x00051eb8, .hires = true },
+ { .clk_out = 6250000, .r = 0x00053555, .hires = true },
+ { .clk_out = 6400000, .r = 0x00055555, .hires = true },
+ { .clk_out = 8192000, .r = 0x0006d3a0, .hires = true },
+ { .clk_out = 9600000, .r = 0x00080000, .hires = true },
+ { .clk_out = 12288000, .r = 0x000a3d70, .hires = true },
+ { .clk_out = 13500000, .r = 0x000b4000, .hires = true },
+ { .clk_out = 19200000, .r = 0x00100000, .hires = true },
+ { .clk_out = 22579200, .r = 0x0012d0e5, .hires = true },
+ { .clk_out = 75000000, .r = 0x003e8000, .hires = true },
+ { .clk_out = 74999999, .r = 0x003e7fff, .hires = true },
+ { .clk_out = 32000000, .r = 0x001aaaaa, .hires = true },
+ { .clk_out = 49152000, .r = 0x0028f5c2, .hires = true },
+ { .clk_out = 45158400, .r = 0x0025a1ca, .hires = true },
+ { /* terminator */}
+};
+
+static const struct cs2600_kunit_ratio_config cs2600_kunit_24_576M_ratios[] = {
+ { .clk_out = 12000000, .r = 0x0007d000, .hires = true },
+ { .clk_out = 8000000, .r = 0x00053555, .hires = true },
+ { .clk_out = 6000000, .r = 0x0003e800, .hires = true },
+ { .clk_out = 24576000, .r = 0x00100000, .hires = true },
+ { .clk_out = 12000001, .r = 0x0007d000, .hires = true },
+ { .clk_out = 12000011, .r = 0x0007d000, .hires = true },
+ { .clk_out = 12000012, .r = 0x0007d000, .hires = true },
+ { .clk_out = 11999999, .r = 0x0007cfff, .hires = true },
+ { .clk_out = 11999988, .r = 0x0007cfff, .hires = true },
+ { .clk_out = 11999987, .r = 0x0007cfff, .hires = true },
+ { .clk_out = 8000004, .r = 0x00053555, .hires = true },
+ { .clk_out = 6144000, .r = 0x00040000, .hires = true },
+ { .clk_out = 6250000, .r = 0x000411aa, .hires = true },
+ { .clk_out = 6400000, .r = 0x00042aaa, .hires = true },
+ { .clk_out = 8192000, .r = 0x00055555, .hires = true },
+ { .clk_out = 9600000, .r = 0x00064000, .hires = true },
+ { .clk_out = 12288000, .r = 0x00080000, .hires = true },
+ { .clk_out = 13500000, .r = 0x0008ca00, .hires = true },
+ { .clk_out = 19200000, .r = 0x000c8000, .hires = true },
+ { .clk_out = 22579200, .r = 0x000eb333, .hires = true },
+ { .clk_out = 75000000, .r = 0x0030d400, .hires = true },
+ { .clk_out = 74999999, .r = 0x0030d3ff, .hires = true },
+ { .clk_out = 32000000, .r = 0x0014d555, .hires = true },
+ { .clk_out = 49152000, .r = 0x00200000, .hires = true },
+ { .clk_out = 45158400, .r = 0x001d6666, .hires = true },
+ { /* terminator */}
+};
+
+static const struct cs2600_kunit_ratio_config cs2600_kunit_25M_ratios[] = {
+ { .clk_out = 12000000, .r = 0x0007ae14, .hires = true },
+ { .clk_out = 8000000, .r = 0x00051eb8, .hires = true },
+ { .clk_out = 6000000, .r = 0, .hires = true },
+ { .clk_out = 24576000, .r = 0x000fba88, .hires = true },
+ { .clk_out = 12000001, .r = 0x0007ae14, .hires = true },
+ { .clk_out = 12000011, .r = 0x0007ae14, .hires = true },
+ { .clk_out = 12000012, .r = 0x0007ae14, .hires = true },
+ { .clk_out = 11999999, .r = 0x0007ae14, .hires = true },
+ { .clk_out = 11999988, .r = 0x0007ae13, .hires = true },
+ { .clk_out = 11999987, .r = 0x0007ae13, .hires = true },
+ { .clk_out = 8000004, .r = 0x00051eb8, .hires = true },
+ { .clk_out = 6144000, .r = 0x0003eea2, .hires = true },
+ { .clk_out = 6250000, .r = 0x00040000, .hires = true },
+ { .clk_out = 6400000, .r = 0x00041893, .hires = true },
+ { .clk_out = 8192000, .r = 0x00053e2d, .hires = true },
+ { .clk_out = 9600000, .r = 0x000624dd, .hires = true },
+ { .clk_out = 12288000, .r = 0x0007dd44, .hires = true },
+ { .clk_out = 13500000, .r = 0x0008a3d7, .hires = true },
+ { .clk_out = 19200000, .r = 0x000c49ba, .hires = true },
+ { .clk_out = 22579200, .r = 0x000e7360, .hires = true },
+ { .clk_out = 75000000, .r = 0x00300000, .hires = true },
+ { .clk_out = 74999999, .r = 0x002fffff, .hires = true },
+ { .clk_out = 32000000, .r = 0x00147ae1, .hires = true },
+ { .clk_out = 49152000, .r = 0x001f7510, .hires = true },
+ { .clk_out = 45158400, .r = 0x001ce6c0, .hires = true },
+ { /* terminator */}
+};
+
+static const struct cs2600_kunit_ratio_config cs2600_kunit_27M_ratios[] = {
+ { .clk_out = 12000000, .r = 0x00071c71, .hires = true },
+ { .clk_out = 8000000, .r = 0x0004bda1, .hires = true },
+ { .clk_out = 6000000, .r = 0, .hires = true },
+ { .clk_out = 24576000, .r = 0x000e9045, .hires = true },
+ { .clk_out = 12000001, .r = 0x00071c71, .hires = true },
+ { .clk_out = 12000011, .r = 0x00071c72, .hires = true },
+ { .clk_out = 12000012, .r = 0x00071c72, .hires = true },
+ { .clk_out = 11999999, .r = 0x00071c71, .hires = true },
+ { .clk_out = 11999988, .r = 0x00071c71, .hires = true },
+ { .clk_out = 11999987, .r = 0x00071c71, .hires = true },
+ { .clk_out = 8000004, .r = 0x0004bda1, .hires = true },
+ { .clk_out = 6144000, .r = 0x0003a411, .hires = true },
+ { .clk_out = 6250000, .r = 0x0003b425, .hires = true },
+ { .clk_out = 6400000, .r = 0x0003cae7, .hires = true },
+ { .clk_out = 8192000, .r = 0x0004dac1, .hires = true },
+ { .clk_out = 9600000, .r = 0x0005b05b, .hires = true },
+ { .clk_out = 12288000, .r = 0x00074822, .hires = true },
+ { .clk_out = 13500000, .r = 0x00080000, .hires = true },
+ { .clk_out = 19200000, .r = 0x000b60b6, .hires = true },
+ { .clk_out = 22579200, .r = 0x000d6159, .hires = true },
+ { .clk_out = 75000000, .r = 0x002c71c7, .hires = true },
+ { .clk_out = 74999999, .r = 0x002c71c7, .hires = true },
+ { .clk_out = 32000000, .r = 0x0012f684, .hires = true },
+ { .clk_out = 49152000, .r = 0x001d208a, .hires = true },
+ { .clk_out = 45158400, .r = 0x001ac2b2, .hires = true },
+ { /* terminator */}
+};
+
+static const struct cs2600_kunit_ratio_config cs2600_kunit_30M_ratios[] = {
+ { .clk_out = 12000000, .r = 0x00066666, .hires = true },
+ { .clk_out = 8000000, .r = 0x00044444, .hires = true },
+ { .clk_out = 6000000, .r = 0, .hires = true },
+ { .clk_out = 24576000, .r = 0x000d1b71, .hires = true },
+ { .clk_out = 12000001, .r = 0x00066666, .hires = true },
+ { .clk_out = 12000011, .r = 0x00066666, .hires = true },
+ { .clk_out = 12000012, .r = 0x00066666, .hires = true },
+ { .clk_out = 11999999, .r = 0x00066666, .hires = true },
+ { .clk_out = 11999988, .r = 0x00066665, .hires = true },
+ { .clk_out = 11999987, .r = 0x00066665, .hires = true },
+ { .clk_out = 8000004, .r = 0x00044444, .hires = true },
+ { .clk_out = 6144000, .r = 0x000346dc, .hires = true },
+ { .clk_out = 6250000, .r = 0x00035555, .hires = true },
+ { .clk_out = 6400000, .r = 0x000369d0, .hires = true },
+ { .clk_out = 8192000, .r = 0x00045e7b, .hires = true },
+ { .clk_out = 9600000, .r = 0x00051eb8, .hires = true },
+ { .clk_out = 12288000, .r = 0x00068db8, .hires = true },
+ { .clk_out = 13500000, .r = 0x00073333, .hires = true },
+ { .clk_out = 19200000, .r = 0x000a3d70, .hires = true },
+ { .clk_out = 22579200, .r = 0x000c0ad0, .hires = true },
+ { .clk_out = 75000000, .r = 0x00280000, .hires = true },
+ { .clk_out = 74999999, .r = 0x0027ffff, .hires = true },
+ { .clk_out = 32000000, .r = 0x00111111, .hires = true },
+ { .clk_out = 49152000, .r = 0x001a36e2, .hires = true },
+ { .clk_out = 45158400, .r = 0x001815a0, .hires = true },
+ { /* terminator */}
+};
+
+static const struct cs2600_kunit_ratio_config cs2600_kunit_75M_ratios[] = {
+ { .clk_out = 12000000, .r = 0x00028f5c, .hires = true },
+ { .clk_out = 8000000, .r = 0x0001b4e8, .hires = true },
+ { .clk_out = 6000000, .r = 0, .hires = true },
+ { .clk_out = 24576000, .r = 0x00053e2d, .hires = true },
+ { .clk_out = 12000001, .r = 0x00028f5c, .hires = true },
+ { .clk_out = 12000011, .r = 0x00028f5c, .hires = true },
+ { .clk_out = 12000012, .r = 0x00028f5c, .hires = true },
+ { .clk_out = 11999999, .r = 0x00028f5c, .hires = true },
+ { .clk_out = 11999988, .r = 0x00028f5b, .hires = true },
+ { .clk_out = 11999987, .r = 0x00028f5b, .hires = true },
+ { .clk_out = 8000004, .r = 0x0001b4e8, .hires = true },
+ { .clk_out = 6144000, .r = 0x00014f8b, .hires = true },
+ { .clk_out = 6250000, .r = 0x00015555, .hires = true },
+ { .clk_out = 6400000, .r = 0x00015d86, .hires = true },
+ { .clk_out = 8192000, .r = 0x0001bf64, .hires = true },
+ { .clk_out = 9600000, .r = 0x00020c49, .hires = true },
+ { .clk_out = 12288000, .r = 0x00029f16, .hires = true },
+ { .clk_out = 13500000, .r = 0x0002e147, .hires = true },
+ { .clk_out = 19200000, .r = 0x00041893, .hires = true },
+ { .clk_out = 22579200, .r = 0x0004d120, .hires = true },
+ { .clk_out = 75000000, .r = 0x00100000, .hires = true },
+ { .clk_out = 74999999, .r = 0x000fffff, .hires = true },
+ { .clk_out = 32000000, .r = 0x0006d3a0, .hires = true },
+ { .clk_out = 49152000, .r = 0x000a7c5a, .hires = true },
+ { .clk_out = 45158400, .r = 0x0009a240, .hires = true },
+ { /* terminator */}
+};
+
+static const struct cs2600_kunit_div_config cs2600_kunit_fsync_divs[] = {
+ { .pll_out = 6144000, .bf_out = 48000, .r = 3 },
+ { .pll_out = 6144000, .bf_out = 96000, .r = 2 },
+ { .pll_out = 6144000, .bf_out = 192000, .r = 1 },
+ { .pll_out = 6144000, .bf_out = 384000, .r = 0 },
+
+ { .pll_out = 12288000, .bf_out = 24000, .r = 5 },
+ { .pll_out = 12288000, .bf_out = 48000, .r = 4 },
+ { .pll_out = 12288000, .bf_out = 96000, .r = 3 },
+ { .pll_out = 12288000, .bf_out = 192000, .r = 2 },
+ { .pll_out = 12288000, .bf_out = 384000, .r = 1 },
+ { .pll_out = 12288000, .bf_out = 768000, .r = 0 },
+
+ { .pll_out = 16384000, .bf_out = 16000, .r = 6 },
+ { .pll_out = 16384000, .bf_out = 32000, .r = 5 },
+ { .pll_out = 16384000, .bf_out = 64000, .r = 4 },
+ { .pll_out = 16384000, .bf_out = 128000, .r = 3 },
+
+ { .pll_out = 24576000, .bf_out = 16000, .r = 10 },
+ { .pll_out = 24576000, .bf_out = 32000, .r = 9 },
+ { .pll_out = 24576000, .bf_out = 48000, .r = 5 },
+ { .pll_out = 24576000, .bf_out = 64000, .r = 8 },
+ { .pll_out = 24576000, .bf_out = 96000, .r = 4 },
+ { .pll_out = 24576000, .bf_out = 128000, .r = 7 },
+ { .pll_out = 24576000, .bf_out = 192000, .r = 3 },
+ { .pll_out = 24576000, .bf_out = 384000, .r = 2 },
+ { .pll_out = 24576000, .bf_out = 768000, .r = 1 },
+
+ { .pll_out = 27648000, .bf_out = 24000, .r = 12 },
+ { .pll_out = 27648000, .bf_out = 48000, .r = 11 },
+
+ { .pll_out = 36864000, .bf_out = 48000, .r = 9 },
+ { .pll_out = 36864000, .bf_out = 96000, .r = 8 },
+ { .pll_out = 36864000, .bf_out = 192000, .r = 7 },
+
+ { .pll_out = 49152000, .bf_out = 48000, .r = 6 },
+ { .pll_out = 49152000, .bf_out = 96000, .r = 5 },
+ { .pll_out = 49152000, .bf_out = 192000, .r = 4 },
+ { .pll_out = 49152000, .bf_out = 384000, .r = 3 },
+ { .pll_out = 49152000, .bf_out = 768000, .r = 2 },
+
+ { .pll_out = 8467200, .bf_out = 44100, .r = 7 },
+
+ { .pll_out = 16934400, .bf_out = 44100, .r = 8 },
+ { .pll_out = 16934400, .bf_out = 88200, .r = 7 },
+
+ { .pll_out = 11289600, .bf_out = 44100, .r = 4 },
+ { .pll_out = 11289600, .bf_out = 88200, .r = 3 },
+ { .pll_out = 11289600, .bf_out = 176400, .r = 2 },
+ { .pll_out = 11289600, .bf_out = 352800, .r = 1 },
+ { .pll_out = 11289600, .bf_out = 705600, .r = 0 },
+
+ { .pll_out = 50803200, .bf_out = 44100, .r = 12 },
+ { .pll_out = 50803200, .bf_out = 88200, .r = 11 },
+
+ { .pll_out = 55296000, .bf_out = 48000, .r = 12 },
+ { .pll_out = 55296000, .bf_out = 96000, .r = 11 },
+
+ { /* terminator */}
+};
+
+static const struct cs2600_kunit_div_config cs2600_kunit_bclk_divs[] = {
+ { .pll_out = 6144000, .bf_out = 1536000, .r = 3 },
+ { .pll_out = 6144000, .bf_out = 2048000, .r = 2 },
+ { .pll_out = 6144000, .bf_out = 3072000, .r = 1 },
+ { .pll_out = 6144000, .bf_out = 6144000, .r = 0 },
+
+ { .pll_out = 12288000, .bf_out = 1536000, .r = 5 },
+ { .pll_out = 12288000, .bf_out = 2048000, .r = 4 },
+ { .pll_out = 12288000, .bf_out = 3072000, .r = 3 },
+ { .pll_out = 12288000, .bf_out = 4096000, .r = 2 },
+ { .pll_out = 12288000, .bf_out = 6144000, .r = 1 },
+ { .pll_out = 12288000, .bf_out = 12288000, .r = 0 },
+
+ { .pll_out = 16384000, .bf_out = 512000, .r = 9 },
+ { .pll_out = 16384000, .bf_out = 1024000, .r = 7 },
+ { .pll_out = 16384000, .bf_out = 2048000, .r = 5 },
+ { .pll_out = 16384000, .bf_out = 4096000, .r = 3 },
+
+ { .pll_out = 24576000, .bf_out = 512000, .r = 10 },
+ { .pll_out = 24576000, .bf_out = 768000, .r = 9 },
+ { .pll_out = 24576000, .bf_out = 1024000, .r = 8 },
+ { .pll_out = 24576000, .bf_out = 1536000, .r = 7 },
+ { .pll_out = 24576000, .bf_out = 2048000, .r = 6 },
+ { .pll_out = 24576000, .bf_out = 3072000, .r = 5 },
+ { .pll_out = 24576000, .bf_out = 4096000, .r = 4 },
+ { .pll_out = 24576000, .bf_out = 6144000, .r = 3 },
+ { .pll_out = 24576000, .bf_out = 8192000, .r = 2 },
+ { .pll_out = 24576000, .bf_out = 12288000, .r = 1 },
+ { .pll_out = 24576000, .bf_out = 24576000, .r = 0 },
+
+ { .pll_out = 27648000, .bf_out = 576000, .r = 10 },
+ { .pll_out = 27648000, .bf_out = 864000, .r = 9 },
+
+ { .pll_out = 36864000, .bf_out = 1536000, .r = 8 },
+ { .pll_out = 36864000, .bf_out = 3072000, .r = 6 },
+ { .pll_out = 36864000, .bf_out = 6144000, .r = 4 },
+
+ { .pll_out = 49152000, .bf_out = 1536000, .r = 9 },
+ { .pll_out = 49152000, .bf_out = 3072000, .r = 7 },
+ { .pll_out = 49152000, .bf_out = 6144000, .r = 5 },
+ { .pll_out = 49152000, .bf_out = 12288000, .r = 3 },
+ { .pll_out = 49152000, .bf_out = 24576000, .r = 1 },
+
+ { .pll_out = 8467200, .bf_out = 1411200, .r = 4 },
+
+ { .pll_out = 16934400, .bf_out = 1411200, .r = 6 },
+ { .pll_out = 16934400, .bf_out = 2822400, .r = 4 },
+
+ { .pll_out = 11289600, .bf_out = 1411200, .r = 5 },
+ { .pll_out = 11289600, .bf_out = 2822400, .r = 3 },
+ { .pll_out = 11289600, .bf_out = 5644800, .r = 1 },
+
+ { .pll_out = 50803200, .bf_out = 1058400, .r = 10 },
+ { .pll_out = 50803200, .bf_out = 2116800, .r = 8 },
+
+ { .pll_out = 55296000, .bf_out = 1152000, .r = 10 },
+ { .pll_out = 55296000, .bf_out = 2304000, .r = 8 },
+
+ { /* terminator */}
+};
+
+static void cs2600_kunit_wait_for_probes(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+
+ KUNIT_ASSERT_NE(test, 0,
+ wait_for_completion_timeout(&priv->consumers_completion,
+ msecs_to_jiffies(1000)));
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->pllout);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->clkout);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->bclk);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->fsync);
+}
+
+static void cs2600_kunit_clock_out_initially_disabled(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int aux2_out_sel;
+
+ /* All clock outputs off */
+ KUNIT_EXPECT_TRUE(test,
+ regmap_test_bits(priv->regmap, CS2600_PLL_CFG1, CS2600_CLK_OUT_DIS));
+ KUNIT_EXPECT_TRUE(test,
+ regmap_test_bits(priv->regmap, CS2600_OUTPUT_CFG1, CS2600_BCLK_OUT_DIS));
+ KUNIT_EXPECT_TRUE(test,
+ regmap_test_bits(priv->regmap, CS2600_OUTPUT_CFG1, CS2600_FSYNC_OUT_DIS));
+
+ /* PLL off */
+ KUNIT_EXPECT_FALSE(test, regmap_test_bits(priv->regmap, CS2600_PLL_CFG1, CS2600_PLL_EN1));
+ KUNIT_EXPECT_FALSE(test, regmap_test_bits(priv->regmap, CS2600_PLL_CFG2, CS2600_PLL_EN2));
+
+ /* AUX2_OUT disabled */
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_OUTPUT_CFG2, &aux2_out_sel));
+ aux2_out_sel = FIELD_GET(CS2600_AUX2OUT_SEL, aux2_out_sel);
+ KUNIT_EXPECT_EQ(test, 0, aux2_out_sel);
+}
+
+static void cs2600_kunit_aux1_out_disabled(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+
+ KUNIT_EXPECT_TRUE(test,
+ regmap_test_bits(priv->regmap, CS2600_PLL_CFG1, CS2600_AUX1_OUT_DIS));
+}
+
+static void cs2600_kunit_aux1_out_enabled(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+
+ KUNIT_EXPECT_FALSE(test,
+ regmap_test_bits(priv->regmap, CS2600_PLL_CFG1, CS2600_AUX1_OUT_DIS));
+}
+
+static void cs2600_kunit_aux1_out_ref_clk_in(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int val;
+
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_aux1_out_ref_clk_in));
+ cs2600_kunit_wait_for_probes(test);
+
+ KUNIT_EXPECT_EQ(test, 0, regmap_read(priv->regmap, CS2600_OUTPUT_CFG2, &val));
+ KUNIT_EXPECT_EQ(test, CS2600_AUX1_OUT_REF_CLK_IN_VAL, FIELD_GET(CS2600_AUX1OUT_SEL, val));
+ cs2600_kunit_aux1_out_enabled(test);
+}
+
+static void cs2600_kunit_aux1_out_clk_in(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int val;
+
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_aux1_out_clk_in));
+ cs2600_kunit_wait_for_probes(test);
+
+ KUNIT_EXPECT_EQ(test, 0, regmap_read(priv->regmap, CS2600_OUTPUT_CFG2, &val));
+ KUNIT_EXPECT_EQ(test, CS2600_AUX1_OUT_CLK_IN_VAL, FIELD_GET(CS2600_AUX1OUT_SEL, val));
+ cs2600_kunit_aux1_out_enabled(test);
+}
+
+static void cs2600_kunit_aux1_out_freq_unlock(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int val;
+
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_aux1_out_freq_unlock));
+ cs2600_kunit_wait_for_probes(test);
+
+ KUNIT_EXPECT_EQ(test, 0, regmap_read(priv->regmap, CS2600_OUTPUT_CFG2, &val));
+ KUNIT_EXPECT_EQ(test, CS2600_AUX1_OUT_FREQ_UNLOCK_VAL, FIELD_GET(CS2600_AUX1OUT_SEL, val));
+ cs2600_kunit_aux1_out_enabled(test);
+}
+
+static void cs2600_kunit_aux1_out_phase_unlock(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int val;
+
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_aux1_out_phase_unlock));
+ cs2600_kunit_wait_for_probes(test);
+
+ KUNIT_EXPECT_EQ(test, 0, regmap_read(priv->regmap, CS2600_OUTPUT_CFG2, &val));
+ KUNIT_EXPECT_EQ(test, CS2600_AUX1_OUT_PHASE_UNLOCK_VAL, FIELD_GET(CS2600_AUX1OUT_SEL, val));
+ cs2600_kunit_aux1_out_enabled(test);
+}
+
+static void cs2600_kunit_aux1_out_clk_in_missing(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int val;
+
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_aux1_out_clk_in_missing));
+ cs2600_kunit_wait_for_probes(test);
+
+ KUNIT_EXPECT_EQ(test, 0, regmap_read(priv->regmap, CS2600_OUTPUT_CFG2, &val));
+ KUNIT_EXPECT_EQ(test, CS2600_AUX1_OUT_NO_CLKIN_VAL, FIELD_GET(CS2600_AUX1OUT_SEL, val));
+ cs2600_kunit_aux1_out_enabled(test);
+}
+
+static void cs2600_kunit_bclk_fsync_not_invert(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int val;
+
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_manual_intosc_only));
+ cs2600_kunit_wait_for_probes(test);
+
+ KUNIT_EXPECT_EQ(test, 0, regmap_read(priv->regmap, CS2600_OUTPUT_CFG1, &val));
+ KUNIT_EXPECT_FALSE(test, FIELD_GET(CS2600_BCLK_INV, val));
+ KUNIT_EXPECT_FALSE(test, FIELD_GET(CS2600_FSYNC_INV, val));
+}
+
+static void cs2600_kunit_bclk_invert(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int val;
+
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_bclk_invert));
+ cs2600_kunit_wait_for_probes(test);
+
+ KUNIT_EXPECT_EQ(test, 0, regmap_read(priv->regmap, CS2600_OUTPUT_CFG1, &val));
+ KUNIT_EXPECT_TRUE(test, FIELD_GET(CS2600_BCLK_INV, val));
+ KUNIT_EXPECT_FALSE(test, FIELD_GET(CS2600_FSYNC_INV, val));
+}
+
+static void cs2600_kunit_fsync_invert(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int val;
+
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_fsync_invert));
+ cs2600_kunit_wait_for_probes(test);
+
+ KUNIT_EXPECT_EQ(test, 0, regmap_read(priv->regmap, CS2600_OUTPUT_CFG1, &val));
+ KUNIT_EXPECT_FALSE(test, FIELD_GET(CS2600_BCLK_INV, val));
+ KUNIT_EXPECT_TRUE(test, FIELD_GET(CS2600_FSYNC_INV, val));
+}
+
+/* Register field value is correct for FSYNC duty cycle 50:50 */
+static void cs2600_kunit_fsync_duty_50_50(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int val;
+
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_manual_intosc_only));
+ cs2600_kunit_wait_for_probes(test);
+
+ KUNIT_EXPECT_EQ(test, 0, regmap_read(priv->regmap, CS2600_OUTPUT_CFG1, &val));
+ KUNIT_EXPECT_EQ(test, 0, FIELD_GET(CS2600_FSYNC_DUTY_CYCLE_MASK, val));
+}
+
+/* Register field value is correct for FSYNC duty cycle of 1 BCLK */
+static void cs2600_kunit_fsync_duty_1(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int val;
+
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_fsync_duty_1));
+ cs2600_kunit_wait_for_probes(test);
+
+ KUNIT_EXPECT_EQ(test, 0, regmap_read(priv->regmap, CS2600_OUTPUT_CFG1, &val));
+ KUNIT_EXPECT_EQ(test, 1, FIELD_GET(CS2600_FSYNC_DUTY_CYCLE_MASK, val));
+}
+
+/* Register field value is correct for FSYNC duty cycle of 2 BCLKs */
+static void cs2600_kunit_fsync_duty_2(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int val;
+
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_fsync_duty_2));
+ cs2600_kunit_wait_for_probes(test);
+
+ KUNIT_EXPECT_EQ(test, 0, regmap_read(priv->regmap, CS2600_OUTPUT_CFG1, &val));
+ KUNIT_EXPECT_EQ(test, 2, FIELD_GET(CS2600_FSYNC_DUTY_CYCLE_MASK, val));
+}
+
+/* Register field value is correct for FSYNC duty cycle of 32 BCLKs */
+static void cs2600_kunit_fsync_duty_32(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int val;
+
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_fsync_duty_32));
+ cs2600_kunit_wait_for_probes(test);
+
+ KUNIT_EXPECT_EQ(test, 0, regmap_read(priv->regmap, CS2600_OUTPUT_CFG1, &val));
+ KUNIT_EXPECT_EQ(test, 6, FIELD_GET(CS2600_FSYNC_DUTY_CYCLE_MASK, val));
+}
+
+/* Test DT renaming of output clocks is applied correctly */
+static void cs2600_kunit_name_output_clocks(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_name_output_clocks));
+ cs2600_kunit_wait_for_probes(test);
+
+ KUNIT_EXPECT_STREQ(test, CS2600_KUNIT_CLKOUT_RENAME, __clk_get_name(priv->clkout));
+ KUNIT_EXPECT_STREQ(test, CS2600_KUNIT_BCLK_RENAME, __clk_get_name(priv->bclk));
+ KUNIT_EXPECT_STREQ(test, CS2600_KUNIT_FSYNC_RENAME, __clk_get_name(priv->fsync));
+}
+
+static void cs2600_kunit_sysclk_source_is_internal(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int val;
+
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG3, &val));
+ KUNIT_EXPECT_EQ(test, 2, FIELD_GET(CS2600_SYSCLK_SRC_MASK, val));
+}
+
+static void cs2600_kunit_sysclk_source_is_ref_clk_in(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int val;
+
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG3, &val));
+ KUNIT_EXPECT_EQ(test, 1, FIELD_GET(CS2600_SYSCLK_SRC_MASK, val));
+}
+
+/* CLKOUT, BCLK_OUT and FSYNC_OUT all have PLL_OUT as their parent clock */
+static void cs2600_kunit_parent_tree(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(priv->pllout));
+ KUNIT_EXPECT_TRUE(test, clk_is_match(priv->pllout, clk_get_parent(priv->clkout)));
+ KUNIT_EXPECT_TRUE(test, clk_is_match(priv->pllout, clk_get_parent(priv->bclk)));
+ KUNIT_EXPECT_TRUE(test, clk_is_match(priv->pllout, clk_get_parent(priv->fsync)));
+}
+
+/* REFLK_CLK_IN is the only parent of the PLL */
+static void cs2600_kunit_single_refclk_parent(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ struct clk_hw *cs2600_pll_clk_hw = __clk_get_hw(clk_get_parent(priv->clkout));
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cs2600_pll_clk_hw);
+ KUNIT_EXPECT_EQ(test, 1, clk_hw_get_num_parents(cs2600_pll_clk_hw));
+ KUNIT_EXPECT_PTR_EQ(test, priv->refclk_hw, clk_hw_get_parent(cs2600_pll_clk_hw));
+}
+
+/*
+ * In manual mode using the internal oscillator, the choice of frequency
+ * references is the internal oscillator or CLK_IN.
+ */
+static void cs2600_kunit_intosc_clkin_parents(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ struct clk_hw *cs2600_pll_clk_hw = __clk_get_hw(clk_get_parent(priv->clkout));
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cs2600_pll_clk_hw);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->clkin_hw);
+ KUNIT_EXPECT_EQ(test, 2, clk_hw_get_num_parents(cs2600_pll_clk_hw));
+ KUNIT_EXPECT_PTR_EQ(test, priv->clkin_hw,
+ clk_hw_get_parent_by_index(cs2600_pll_clk_hw, 1));
+}
+
+/*
+ * In smart mode the parent source isn't selectable and the only parent
+ * should be CLK_IN.
+ */
+static void cs2600_kunit_smart_parents(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ struct clk_hw *cs2600_pll_clk_hw = __clk_get_hw(clk_get_parent(priv->clkout));
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cs2600_pll_clk_hw);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->clkin_hw);
+ KUNIT_EXPECT_EQ(test, 1, clk_hw_get_num_parents(cs2600_pll_clk_hw));
+ KUNIT_EXPECT_PTR_EQ(test, priv->clkin_hw, clk_hw_get_parent(cs2600_pll_clk_hw));
+}
+
+/* Assert that PLL has both REF_CLK_IN and CLK_IN as parents */
+static void cs2600_kunit_refclk_clkin_parents(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ struct clk_hw *cs2600_pll_clk_hw = __clk_get_hw(clk_get_parent(priv->clkout));
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cs2600_pll_clk_hw);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->clkin_hw);
+ KUNIT_EXPECT_EQ(test, 2, clk_hw_get_num_parents(cs2600_pll_clk_hw));
+ KUNIT_EXPECT_PTR_EQ(test, priv->refclk_hw,
+ clk_hw_get_parent_by_index(cs2600_pll_clk_hw, 0));
+ KUNIT_EXPECT_PTR_EQ(test, priv->clkin_hw,
+ clk_hw_get_parent_by_index(cs2600_pll_clk_hw, 1));
+}
+
+/* REF_CLK divider is set correctly to give a valid SYSCLK */
+static void cs2600_kunit_ref_clk_divider(struct kunit *test)
+{
+ const struct cs2600_kunit_params *params = test->param_value;
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int val, sysclk_rate;
+
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG3, &val));
+ switch (FIELD_GET(CS2600_REF_CLK_IN_DIV_MASK, val)) {
+ case 0:
+ sysclk_rate = params->ref_clk / 4;
+ break;
+ case 1:
+ sysclk_rate = params->ref_clk / 2;
+ break;
+ case 2:
+ sysclk_rate = params->ref_clk;
+ break;
+ default:
+ KUNIT_FAIL(test, "Illegal REF_CLK_IN_DIV: %#lx",
+ FIELD_GET(CS2600_REF_CLK_IN_DIV_MASK, val));
+ return;
+ }
+
+ KUNIT_EXPECT_GE(test, sysclk_rate, 8000000);
+ KUNIT_EXPECT_LE(test, sysclk_rate, 18750000);
+}
+
+static unsigned int cs2600_kunit_get_ratio_val(struct kunit *test,
+ unsigned int ratio_sel)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int addr1, addr2, val1, val2;
+
+ switch (ratio_sel) {
+ case 0:
+ addr1 = CS2600_RATIO1_1;
+ addr2 = CS2600_RATIO1_2;
+ break;
+ case 1:
+ addr1 = CS2600_RATIO2_1;
+ addr2 = CS2600_RATIO2_2;
+ break;
+ default:
+ KUNIT_ASSERT_LT(test, ratio_sel, 1);
+ return 0;
+ }
+
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, addr1, &val1));
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, addr2, &val2));
+
+ return (val1 << 16) | val2;
+}
+
+/* Synthesizer ratio is set correctly */
+static void cs2600_kunit_synth_ratio(struct kunit *test)
+{
+ const struct cs2600_kunit_params *params = test->param_value;
+ const struct cs2600_kunit_ratio_config *ratios = params->s_ratios;
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int ratio_sel, pll_cfg2, ratio;
+ int i;
+
+ KUNIT_ASSERT_NOT_NULL(test, ratios);
+
+ for (i = 0; ratios[i].clk_out != 0; i++) {
+ /* 6 MHz is out-of-range for some input clocks */
+ if (ratios[i].r == 0) {
+ KUNIT_ASSERT_EQ(test, ratios[i].clk_out, 6000000);
+ continue;
+ }
+
+ KUNIT_EXPECT_EQ_MSG(test, 0,
+ clk_set_rate(priv->clkout, ratios[i].clk_out),
+ "clk_out:%u s:%#x\n",
+ ratios[i].clk_out, ratios[i].r);
+ KUNIT_EXPECT_EQ_MSG(test, 0,
+ clk_prepare_enable_kunit(test, priv->clkout),
+ "clk_out:%u s:%#x\n",
+ ratios[i].clk_out, ratios[i].r);
+
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG1, &ratio_sel));
+ ratio_sel = FIELD_GET(CS2600_S_RATIO_SEL_MASK, ratio_sel);
+ ratio = cs2600_kunit_get_ratio_val(test, ratio_sel);
+ KUNIT_EXPECT_NE_MSG(test, 0, ratio,
+ "clk_out:%u s:%#x\n",
+ ratios[i].clk_out, ratios[i].r);
+
+ /* M_RATIO_SEL must be the same as S_RATIO_SEL */
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG2, &pll_cfg2));
+ KUNIT_EXPECT_EQ(test, ratio_sel, FIELD_GET(CS2600_M_RATIO_SEL_MASK, pll_cfg2));
+
+ /* PLL_MODE must be synthesizer */
+ KUNIT_EXPECT_EQ(test, 0, FIELD_GET(CS2600_PLL_MODE_SEL, pll_cfg2));
+
+ KUNIT_EXPECT_EQ_MSG(test, ratios[i].r, ratio,
+ "clk_out:%u s:%#x\n",
+ ratios[i].clk_out, ratios[i].r);
+ }
+}
+
+/* Multiplier mode ratio is set correctly */
+static void cs2600_kunit_mult_ratio(struct kunit *test)
+{
+ const struct cs2600_kunit_params *params = test->param_value;
+ const struct cs2600_kunit_ratio_config *ratios = params->m_ratios;
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int ratio_sel, pll_cfg1, pll_cfg2, pll_cfg3, ratio;
+ int i;
+
+ KUNIT_ASSERT_NOT_NULL(test, ratios);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->clkin_hw);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->clkin_hw->clk);
+
+ /* Switch parent to clk_in */
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->pllout);
+ KUNIT_EXPECT_EQ(test, 0, clk_set_parent(priv->pllout, priv->clkin_hw->clk));
+
+ for (i = 0; ratios[i].clk_out != 0; i++) {
+ /* 6 MHz is out-of-range for some input clocks */
+ if (ratios[i].r == 0) {
+ KUNIT_ASSERT_EQ(test, ratios[i].clk_out, 6000000);
+ continue;
+ }
+
+ KUNIT_EXPECT_EQ_MSG(test, 0,
+ clk_set_rate(priv->clkout, ratios[i].clk_out),
+ "clk_out:%u m:%#x\n",
+ ratios[i].clk_out, ratios[i].r);
+ KUNIT_EXPECT_EQ_MSG(test, 0,
+ clk_prepare_enable_kunit(test, priv->clkout),
+ "clk_out:%u m:%#x\n",
+ ratios[i].clk_out, ratios[i].r);
+
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG2, &pll_cfg2));
+ ratio_sel = FIELD_GET(CS2600_M_RATIO_SEL_MASK, pll_cfg2);
+ ratio = cs2600_kunit_get_ratio_val(test, ratio_sel);
+ KUNIT_EXPECT_NE_MSG(test, 0, ratio,
+ "clk_out:%u m:%#x\n",
+ ratios[i].clk_out, ratios[i].r);
+
+ /* S_RATIO_SEL must be the same as M_RATIO_SEL */
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG1, &pll_cfg1));
+ KUNIT_EXPECT_EQ(test, ratio_sel, FIELD_GET(CS2600_S_RATIO_SEL_MASK, pll_cfg1));
+
+ /* PLL_MODE must be multiplier */
+ KUNIT_EXPECT_EQ(test, 1, FIELD_GET(CS2600_PLL_MODE_SEL, pll_cfg2));
+
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG3, &pll_cfg3));
+ KUNIT_EXPECT_EQ(test, ratios[i].hires, FIELD_GET(CS2600_RATIO_CFG, pll_cfg3));
+
+ KUNIT_EXPECT_EQ_MSG(test, ratios[i].r, ratio,
+ "clk_out:%u m:%#x\n",
+ ratios[i].clk_out, ratios[i].r);
+ }
+}
+
+/* Synth and mult ratios are set correct in smart mode. */
+static void cs2600_kunit_smart_ratio(struct kunit *test)
+{
+ const struct cs2600_kunit_params *params = test->param_value;
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int ratio_sel, pll_cfg1, pll_cfg2, pll_cfg3, ratio;
+ u64 actual_fout_20_20;
+ int i;
+
+ KUNIT_ASSERT_NOT_NULL(test, params->m_ratios);
+ KUNIT_ASSERT_NOT_NULL(test, params->s_ratios);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->clkin_hw);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->clkin_hw->clk);
+
+ for (i = 0;
+ (params->s_ratios[i].clk_out != 0) && (params->m_ratios[i].clk_out != 0);
+ i++) {
+ /* Sanity check our test data */
+ KUNIT_ASSERT_EQ(test, params->s_ratios[i].clk_out, params->m_ratios[i].clk_out);
+
+ /* 6 MHz is out-of-range for some input clocks */
+ if ((params->m_ratios[i].r == 0) || (params->s_ratios[i].r == 0)) {
+ KUNIT_ASSERT_EQ(test, params->m_ratios[i].clk_out, 6000000);
+ continue;
+ }
+
+ KUNIT_EXPECT_EQ_MSG(test, 0,
+ clk_set_rate(priv->clkout, params->m_ratios[i].clk_out),
+ "clk_out:%u\n", params->m_ratios[i].clk_out);
+ KUNIT_EXPECT_EQ_MSG(test, 0,
+ clk_prepare_enable_kunit(test, priv->clkout),
+ "clk_out:%u\n", params->m_ratios[i].clk_out);
+
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG1, &pll_cfg1));
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG2, &pll_cfg2));
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG3, &pll_cfg3));
+
+ /* In smart mode S_RATIO_SEL must be different from M_RATIO_SEL */
+ KUNIT_EXPECT_NE(test,
+ FIELD_GET(CS2600_M_RATIO_SEL_MASK, pll_cfg2),
+ FIELD_GET(CS2600_S_RATIO_SEL_MASK, pll_cfg1));
+
+ ratio_sel = FIELD_GET(CS2600_M_RATIO_SEL_MASK, pll_cfg2);
+ ratio = cs2600_kunit_get_ratio_val(test, ratio_sel);
+ KUNIT_EXPECT_NE_MSG(test, 0, ratio,
+ "clk_out:%u\n", params->m_ratios[i].clk_out);
+
+ KUNIT_EXPECT_EQ(test, params->m_ratios[i].hires,
+ FIELD_GET(CS2600_RATIO_CFG, pll_cfg3));
+
+ KUNIT_EXPECT_EQ_MSG(test, params->m_ratios[i].r, ratio,
+ "clk_out:%u\n", params->m_ratios[i].clk_out);
+
+ if (params->m_ratios[i].hires)
+ actual_fout_20_20 = (u64)ratio * priv->clkin_rate;
+ else
+ actual_fout_20_20 = ((u64)ratio * priv->clkin_rate) << 8;
+
+ ratio_sel = FIELD_GET(CS2600_S_RATIO_SEL_MASK, pll_cfg1);
+ ratio = cs2600_kunit_get_ratio_val(test, ratio_sel);
+ KUNIT_EXPECT_NE_MSG(test, 0, ratio,
+ "clk_out:%u\n", params->s_ratios[i].clk_out);
+
+ /*
+ * The synth ratio is based on the actual mult ratio output,
+ * not the original requested target rate, so the ratio is not
+ * necessarily REFCLK_IN:TARGET_RATE.
+ * Other test cases prove that the synth ratio calculation is
+ * correct so here only test that the actual mult output rate
+ * lies somewhere within the chosen synth ratio band.
+ */
+ KUNIT_EXPECT_GE_MSG(test, actual_fout_20_20,
+ (u64)ratio * priv->refclk_rate,
+ "clk_out:%u\n", params->s_ratios[i].clk_out);
+ KUNIT_EXPECT_LT_MSG(test, actual_fout_20_20,
+ (u64)(ratio + 1) * priv->refclk_rate,
+ "clk_out:%u\n", params->s_ratios[i].clk_out);
+ }
+}
+
+/*
+ * In smart mode with clkin-only the synth ratio should be 0 to suppress
+ * output while REF_CLK_IN is the frequency reference.
+ */
+static void cs2600_kunit_smart_clkin_only_ratio(struct kunit *test)
+{
+ const struct cs2600_kunit_params *params = test->param_value;
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int ratio_sel, pll_cfg1, pll_cfg2, pll_cfg3, ratio;
+ u64 actual_fout_20_20;
+ int i;
+
+ KUNIT_ASSERT_NOT_NULL(test, params->m_ratios);
+ KUNIT_ASSERT_NOT_NULL(test, params->s_ratios);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->clkin_hw);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->clkin_hw->clk);
+
+ for (i = 0;
+ (params->s_ratios[i].clk_out != 0) && (params->m_ratios[i].clk_out != 0);
+ i++) {
+ /* 6 MHz is out-of-range for some input clocks */
+ if ((params->m_ratios[i].r == 0) || (params->s_ratios[i].r == 0)) {
+ KUNIT_ASSERT_EQ(test, params->m_ratios[i].clk_out, 6000000);
+ continue;
+ }
+
+ KUNIT_EXPECT_EQ_MSG(test, 0,
+ clk_set_rate(priv->clkout, params->m_ratios[i].clk_out),
+ "clk_out:%u\n", params->m_ratios[i].clk_out);
+ KUNIT_EXPECT_EQ_MSG(test, 0,
+ clk_prepare_enable_kunit(test, priv->clkout),
+ "clk_out:%u\n", params->m_ratios[i].clk_out);
+
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG1, &pll_cfg1));
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG2, &pll_cfg2));
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG3, &pll_cfg3));
+
+ /* In smart mode S_RATIO_SEL must be different from M_RATIO_SEL */
+ KUNIT_EXPECT_NE(test,
+ FIELD_GET(CS2600_M_RATIO_SEL_MASK, pll_cfg2),
+ FIELD_GET(CS2600_S_RATIO_SEL_MASK, pll_cfg1));
+
+ ratio_sel = FIELD_GET(CS2600_M_RATIO_SEL_MASK, pll_cfg2);
+ ratio = cs2600_kunit_get_ratio_val(test, ratio_sel);
+ KUNIT_EXPECT_NE_MSG(test, 0, ratio,
+ "clk_out:%u\n", params->m_ratios[i].clk_out);
+
+ KUNIT_EXPECT_EQ(test, params->m_ratios[i].hires,
+ FIELD_GET(CS2600_RATIO_CFG, pll_cfg3));
+
+ KUNIT_EXPECT_EQ_MSG(test, params->m_ratios[i].r, ratio,
+ "clk_out:%u\n", params->m_ratios[i].clk_out);
+
+ if (params->m_ratios[i].hires)
+ actual_fout_20_20 = (u64)ratio * priv->clkin_rate;
+ else
+ actual_fout_20_20 = ((u64)ratio * priv->clkin_rate) << 8;
+
+ /* In CLK_IN-only mode the S_RATIO must be zero */
+ ratio_sel = FIELD_GET(CS2600_S_RATIO_SEL_MASK, pll_cfg1);
+ ratio = cs2600_kunit_get_ratio_val(test, ratio_sel);
+ KUNIT_EXPECT_EQ_MSG(test, 0, ratio,
+ "clk_out:%u\n", params->s_ratios[i].clk_out);
+ }
+}
+
+/* Change parent without changing output rate */
+static void cs2600_kunit_change_parent_same_clkout(struct kunit *test)
+{
+ const struct cs2600_kunit_params *params = test->param_value;
+ struct cs2600_kunit_priv *priv = test->priv;
+ struct clk *refclk = clk_hw_get_clk_kunit(test, priv->refclk_hw, NULL);
+ struct clk *clkin = clk_hw_get_clk_kunit(test, priv->clkin_hw, NULL);
+ unsigned int ratio_sel, pll_cfg1, pll_cfg2, pll_cfg3, ratio;
+ int i;
+
+ KUNIT_ASSERT_NOT_NULL(test, params->s_ratios);
+ KUNIT_ASSERT_NOT_NULL(test, params->m_ratios);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->pllout);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, refclk);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, clkin);
+
+ for (i = 0;
+ (params->s_ratios[i].clk_out != 0) && (params->m_ratios[i].clk_out != 0);
+ i++) {
+ /* Sanity check our test data */
+ KUNIT_ASSERT_EQ(test, params->s_ratios[i].clk_out, params->m_ratios[i].clk_out);
+
+ /* 6 MHz is out-of-range for some input clocks */
+ if ((params->m_ratios[i].r == 0) || (params->s_ratios[i].r == 0)) {
+ KUNIT_ASSERT_EQ(test, params->m_ratios[i].clk_out, 6000000);
+ continue;
+ }
+
+ /* REF_CLK_IN parent should be synth mode */
+ KUNIT_EXPECT_EQ(test, 0, clk_set_parent(priv->pllout, refclk));
+ KUNIT_EXPECT_EQ_MSG(test, 0,
+ clk_set_rate(priv->clkout, params->s_ratios[i].clk_out),
+ "clk_out:%u\n", params->s_ratios[i].clk_out);
+
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG1, &pll_cfg1));
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG2, &pll_cfg2));
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG3, &pll_cfg3));
+
+ KUNIT_EXPECT_EQ(test, 0, FIELD_GET(CS2600_PLL_MODE_SEL, pll_cfg2));
+ KUNIT_EXPECT_EQ(test, FIELD_GET(CS2600_S_RATIO_SEL_MASK, pll_cfg1),
+ FIELD_GET(CS2600_M_RATIO_SEL_MASK, pll_cfg2));
+
+ ratio_sel = FIELD_GET(CS2600_S_RATIO_SEL_MASK, pll_cfg1);
+ ratio = cs2600_kunit_get_ratio_val(test, ratio_sel);
+ KUNIT_EXPECT_EQ_MSG(test, params->s_ratios[i].r, ratio,
+ "clk_out:%u\n", params->s_ratios[i].clk_out);
+
+ /*
+ * Change to CLK_IN without changing rate, ratio should be
+ * changed to multiplier mode.
+ */
+ KUNIT_EXPECT_EQ(test, 0, clk_set_parent(priv->pllout, clkin));
+
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG1, &pll_cfg1));
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG2, &pll_cfg2));
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG3, &pll_cfg3));
+
+ KUNIT_EXPECT_EQ(test, 1, FIELD_GET(CS2600_PLL_MODE_SEL, pll_cfg2));
+ KUNIT_EXPECT_EQ(test, FIELD_GET(CS2600_S_RATIO_SEL_MASK, pll_cfg1),
+ FIELD_GET(CS2600_M_RATIO_SEL_MASK, pll_cfg2));
+
+ /* Ratio should have changed if reference frequency has changed */
+ if (clk_get_rate(refclk) != clk_get_rate(clkin)) {
+ ratio_sel = FIELD_GET(CS2600_M_RATIO_SEL_MASK, pll_cfg2);
+ ratio = cs2600_kunit_get_ratio_val(test, ratio_sel);
+ KUNIT_EXPECT_NE_MSG(test, params->s_ratios[i].r, ratio,
+ "clk_out:%u\n", params->s_ratios[i].clk_out);
+ }
+
+ /* clk_set_rate() should recalculate based on new parent frequency */
+ KUNIT_EXPECT_EQ_MSG(test, 0,
+ clk_set_rate(priv->clkout, params->m_ratios[i].clk_out),
+ "clk_out:%u\n", params->m_ratios[i].clk_out);
+
+ ratio_sel = FIELD_GET(CS2600_M_RATIO_SEL_MASK, pll_cfg2);
+ ratio = cs2600_kunit_get_ratio_val(test, ratio_sel);
+ KUNIT_EXPECT_EQ_MSG(test, params->m_ratios[i].r, ratio,
+ "clk_out:%u\n", params->m_ratios[i].clk_out);
+
+ /*
+ * Change back to default parent and it should be synth mode again
+ */
+ KUNIT_EXPECT_EQ(test, 0, clk_set_parent(priv->pllout, refclk));
+
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG1, &pll_cfg1));
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG2, &pll_cfg2));
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG3, &pll_cfg3));
+
+ KUNIT_EXPECT_EQ(test, 0, FIELD_GET(CS2600_PLL_MODE_SEL, pll_cfg2));
+ KUNIT_EXPECT_EQ(test, FIELD_GET(CS2600_S_RATIO_SEL_MASK, pll_cfg1),
+ FIELD_GET(CS2600_M_RATIO_SEL_MASK, pll_cfg2));
+
+ /* Ratio should have changed if reference frequency has changed */
+ if (clk_get_rate(refclk) != clk_get_rate(clkin)) {
+ ratio_sel = FIELD_GET(CS2600_S_RATIO_SEL_MASK, pll_cfg1);
+ ratio = cs2600_kunit_get_ratio_val(test, ratio_sel);
+ KUNIT_EXPECT_NE_MSG(test, params->m_ratios[i].r, ratio,
+ "clk_out:%u\n", params->s_ratios[i].clk_out);
+ }
+
+ /* clk_set_rate() should recalculate based on new parent frequency */
+ KUNIT_EXPECT_EQ_MSG(test, 0,
+ clk_set_rate(priv->clkout, params->s_ratios[i].clk_out),
+ "clk_out:%u\n", params->s_ratios[i].clk_out);
+
+ ratio_sel = FIELD_GET(CS2600_S_RATIO_SEL_MASK, pll_cfg1);
+ ratio = cs2600_kunit_get_ratio_val(test, ratio_sel);
+ KUNIT_EXPECT_EQ_MSG(test, params->s_ratios[i].r, ratio,
+ "clk_out:%u\n", params->m_ratios[i].clk_out);
+ }
+}
+
+/* PLL_EN bits match logical clock enable/disable state */
+static void cs2600_kunit_pll_enable_disable(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+
+ KUNIT_EXPECT_FALSE(test, regmap_test_bits(priv->regmap, CS2600_PLL_CFG1, CS2600_PLL_EN1));
+ KUNIT_EXPECT_FALSE(test, regmap_test_bits(priv->regmap, CS2600_PLL_CFG2, CS2600_PLL_EN2));
+
+ KUNIT_EXPECT_EQ(test, 0, clk_set_rate(priv->pllout, 12000000));
+ KUNIT_EXPECT_EQ(test, 0, clk_prepare_enable(priv->pllout));
+ KUNIT_ASSERT_EQ(test, 0, kunit_add_action_or_reset(test,
+ clk_disable_unprepare_wrapper,
+ priv->pllout));
+
+ KUNIT_EXPECT_TRUE(test, regmap_test_bits(priv->regmap, CS2600_PLL_CFG1, CS2600_PLL_EN1));
+ KUNIT_EXPECT_TRUE(test, regmap_test_bits(priv->regmap, CS2600_PLL_CFG2, CS2600_PLL_EN2));
+
+ kunit_release_action(test, clk_disable_unprepare_wrapper, priv->pllout);
+ KUNIT_EXPECT_FALSE(test, regmap_test_bits(priv->regmap, CS2600_PLL_CFG1, CS2600_PLL_EN1));
+ KUNIT_EXPECT_FALSE(test, regmap_test_bits(priv->regmap, CS2600_PLL_CFG2, CS2600_PLL_EN2));
+}
+
+/* Clock prepare fails if PLL does not lock */
+static void cs2600_kunit_pll_no_lock(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+
+ priv->force_unlock_status = true;
+
+ KUNIT_EXPECT_EQ(test, 0, clk_set_rate(priv->pllout, 12000000));
+ KUNIT_EXPECT_LT(test, clk_prepare_enable(priv->pllout), 0);
+
+ KUNIT_EXPECT_FALSE(test, regmap_test_bits(priv->regmap, CS2600_PLL_CFG1, CS2600_PLL_EN1));
+ KUNIT_EXPECT_FALSE(test, regmap_test_bits(priv->regmap, CS2600_PLL_CFG2, CS2600_PLL_EN2));
+}
+
+/* CLK_OUT_DIS bit matches logical clock enable/disable state */
+static void cs2600_kunit_clk_out_enable_disable(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+
+ KUNIT_EXPECT_TRUE(test, regmap_test_bits(priv->regmap, CS2600_PLL_CFG1,
+ CS2600_CLK_OUT_DIS));
+
+ KUNIT_EXPECT_EQ(test, 0, clk_set_rate(priv->clkout, 12000000));
+ KUNIT_EXPECT_EQ(test, 0, clk_prepare_enable(priv->clkout));
+ KUNIT_ASSERT_EQ(test, 0, kunit_add_action_or_reset(test,
+ clk_disable_unprepare_wrapper,
+ priv->clkout));
+
+ KUNIT_EXPECT_FALSE(test, regmap_test_bits(priv->regmap, CS2600_PLL_CFG1,
+ CS2600_CLK_OUT_DIS));
+
+ kunit_release_action(test, clk_disable_unprepare_wrapper, priv->clkout);
+ KUNIT_EXPECT_TRUE(test, regmap_test_bits(priv->regmap, CS2600_PLL_CFG1,
+ CS2600_CLK_OUT_DIS));
+}
+
+/*
+ * BCLK_OUT_DIS bit matches logical clock enable/disable state
+ * and enable/disable of BCLK clk does not affect FSYNC_OUT_DIS bit.
+ */
+static void cs2600_kunit_bclk_enable_disable(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+
+ KUNIT_EXPECT_TRUE(test, regmap_test_bits(priv->regmap, CS2600_OUTPUT_CFG1,
+ CS2600_BCLK_OUT_DIS));
+ KUNIT_EXPECT_TRUE(test, regmap_test_bits(priv->regmap, CS2600_OUTPUT_CFG1,
+ CS2600_FSYNC_OUT_DIS));
+
+ KUNIT_EXPECT_EQ(test, 0, clk_set_rate(priv->bclk, 1500000));
+ KUNIT_EXPECT_EQ(test, 0, clk_prepare_enable(priv->bclk));
+ KUNIT_ASSERT_EQ(test, 0, kunit_add_action_or_reset(test,
+ clk_disable_unprepare_wrapper,
+ priv->bclk));
+
+ KUNIT_EXPECT_FALSE(test, regmap_test_bits(priv->regmap, CS2600_OUTPUT_CFG1,
+ CS2600_BCLK_OUT_DIS));
+ KUNIT_EXPECT_TRUE(test, regmap_test_bits(priv->regmap, CS2600_OUTPUT_CFG1,
+ CS2600_FSYNC_OUT_DIS));
+
+ kunit_release_action(test, clk_disable_unprepare_wrapper, priv->bclk);
+ KUNIT_EXPECT_TRUE(test, regmap_test_bits(priv->regmap, CS2600_OUTPUT_CFG1,
+ CS2600_BCLK_OUT_DIS));
+ KUNIT_EXPECT_TRUE(test, regmap_test_bits(priv->regmap, CS2600_OUTPUT_CFG1,
+ CS2600_FSYNC_OUT_DIS));
+}
+
+/* BCLK divider register value is correct */
+static void cs2600_kunit_bclk_divider(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int out_cfg1, bclk_div, fdelta;
+ int i;
+
+ for (i = 0; cs2600_kunit_bclk_divs[i].pll_out != 0; i++) {
+ /* PLL_OUT is the input clock to the BCLK divider */
+ KUNIT_EXPECT_EQ_MSG(test, 0,
+ clk_set_rate(priv->pllout, cs2600_kunit_bclk_divs[i].pll_out),
+ "pll_out:%u bclk:%u\n",
+ cs2600_kunit_bclk_divs[i].pll_out,
+ cs2600_kunit_bclk_divs[i].bf_out);
+
+ KUNIT_EXPECT_EQ_MSG(test, 0,
+ clk_set_rate(priv->bclk, cs2600_kunit_bclk_divs[i].bf_out),
+ "clk_out:%u bclk:%u\n",
+ cs2600_kunit_bclk_divs[i].pll_out,
+ cs2600_kunit_bclk_divs[i].bf_out);
+
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_OUTPUT_CFG1, &out_cfg1));
+ bclk_div = FIELD_GET(CS2600_BCLK_DIV_MASK, out_cfg1);
+ KUNIT_EXPECT_EQ_MSG(test, bclk_div, cs2600_kunit_bclk_divs[i].r,
+ "clk_out:%u bclk:%u\n",
+ cs2600_kunit_bclk_divs[i].pll_out,
+ cs2600_kunit_bclk_divs[i].bf_out);
+
+ /*
+ * Actual reported frequency can be slightly out because of
+ * PLL resolution, but should be within worst-case PPM.
+ */
+ fdelta = (cs2600_kunit_bclk_divs[i].bf_out * CS2600_20_12_PPM) >> 20;
+ KUNIT_EXPECT_GE_MSG(test, clk_get_rate(priv->bclk),
+ cs2600_kunit_bclk_divs[i].bf_out - fdelta,
+ "clk_out:%u bclk:%u\n",
+ cs2600_kunit_bclk_divs[i].pll_out,
+ cs2600_kunit_bclk_divs[i].bf_out);
+ KUNIT_EXPECT_LE_MSG(test, clk_get_rate(priv->bclk),
+ cs2600_kunit_bclk_divs[i].bf_out,
+ "clk_out:%u bclk:%u\n",
+ cs2600_kunit_bclk_divs[i].pll_out,
+ cs2600_kunit_bclk_divs[i].bf_out);
+ }
+}
+
+/*
+ * FSYNC_OUT_DIS bit matches logical clock enable/disable state
+ * and enable/disable of FSYNC clk does not affect BCLK_OUT_DIS bit.
+ */
+static void cs2600_kunit_fsync_enable_disable(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+
+ KUNIT_EXPECT_TRUE(test, regmap_test_bits(priv->regmap, CS2600_OUTPUT_CFG1,
+ CS2600_FSYNC_OUT_DIS));
+ KUNIT_EXPECT_TRUE(test, regmap_test_bits(priv->regmap, CS2600_OUTPUT_CFG1,
+ CS2600_BCLK_OUT_DIS));
+
+ KUNIT_EXPECT_EQ(test, 0, clk_set_rate(priv->fsync, 48000));
+ KUNIT_EXPECT_EQ(test, 0, clk_prepare_enable(priv->fsync));
+ KUNIT_ASSERT_EQ(test, 0, kunit_add_action_or_reset(test,
+ clk_disable_unprepare_wrapper,
+ priv->fsync));
+
+ KUNIT_EXPECT_FALSE(test, regmap_test_bits(priv->regmap, CS2600_OUTPUT_CFG1,
+ CS2600_FSYNC_OUT_DIS));
+ KUNIT_EXPECT_TRUE(test, regmap_test_bits(priv->regmap, CS2600_OUTPUT_CFG1,
+ CS2600_BCLK_OUT_DIS));
+
+ kunit_release_action(test, clk_disable_unprepare_wrapper, priv->fsync);
+ KUNIT_EXPECT_TRUE(test, regmap_test_bits(priv->regmap, CS2600_OUTPUT_CFG1,
+ CS2600_FSYNC_OUT_DIS));
+ KUNIT_EXPECT_TRUE(test, regmap_test_bits(priv->regmap, CS2600_OUTPUT_CFG1,
+ CS2600_BCLK_OUT_DIS));
+}
+
+/* FSYNC divider register value is correct */
+static void cs2600_kunit_fsync_divider(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int out_cfg1, fsync_div, fdelta;
+ int i;
+
+ for (i = 0; cs2600_kunit_fsync_divs[i].pll_out != 0; i++) {
+ /* PLL_OUT is the input clock to the FSYNC divider */
+ KUNIT_EXPECT_EQ_MSG(test, 0,
+ clk_set_rate(priv->pllout, cs2600_kunit_fsync_divs[i].pll_out),
+ "pll_out:%u fsync:%u\n",
+ cs2600_kunit_fsync_divs[i].pll_out,
+ cs2600_kunit_fsync_divs[i].bf_out);
+
+ KUNIT_EXPECT_EQ_MSG(test, 0,
+ clk_set_rate(priv->fsync, cs2600_kunit_fsync_divs[i].bf_out),
+ "clk_out:%u fsync:%u\n",
+ cs2600_kunit_fsync_divs[i].pll_out,
+ cs2600_kunit_fsync_divs[i].bf_out);
+
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_OUTPUT_CFG1, &out_cfg1));
+ fsync_div = FIELD_GET(CS2600_FSYNC_DIV_MASK, out_cfg1);
+ KUNIT_EXPECT_EQ_MSG(test, fsync_div, cs2600_kunit_fsync_divs[i].r,
+ "clk_out:%u fsync:%u\n",
+ cs2600_kunit_fsync_divs[i].pll_out,
+ cs2600_kunit_fsync_divs[i].bf_out);
+
+ /*
+ * Actual reported frequency can be slightly out because of
+ * PLL resolution, but should be within worst-case PPM.
+ */
+ fdelta = (cs2600_kunit_fsync_divs[i].bf_out * CS2600_20_12_PPM) >> 20;
+ KUNIT_EXPECT_GE_MSG(test, clk_get_rate(priv->fsync),
+ cs2600_kunit_fsync_divs[i].bf_out - fdelta,
+ "clk_out:%u fsync:%u\n",
+ cs2600_kunit_fsync_divs[i].pll_out,
+ cs2600_kunit_fsync_divs[i].bf_out);
+
+ KUNIT_EXPECT_LE_MSG(test, clk_get_rate(priv->fsync),
+ cs2600_kunit_fsync_divs[i].bf_out,
+ "clk_out:%u fsync:%u\n",
+ cs2600_kunit_fsync_divs[i].pll_out,
+ cs2600_kunit_fsync_divs[i].bf_out);
+ }
+}
+
+/* assigned-clock-rates in DT is applied to the clocks */
+static void cs2600_kunit_assigned_clock_rates(struct kunit *test)
+{
+ struct cs2600_kunit_priv *priv = test->priv;
+ unsigned int out_cfg1, div;
+
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_assigned));
+ cs2600_kunit_wait_for_probes(test);
+
+ KUNIT_EXPECT_EQ(test, CS2600_KUNIT_FSYNC_ASSIGNED, clk_get_rate(priv->fsync));
+ KUNIT_EXPECT_EQ(test, CS2600_KUNIT_BCLK_ASSIGNED, clk_get_rate(priv->bclk));
+ KUNIT_EXPECT_EQ(test, CS2600_KUNIT_CLKOUT_ASSIGNED, clk_get_rate(priv->clkout));
+
+ KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_OUTPUT_CFG1, &out_cfg1));
+ div = FIELD_GET(CS2600_FSYNC_DIV_MASK, out_cfg1);
+ KUNIT_EXPECT_EQ(test, div, CS2600_KUNIT_FSYNC_DIV_INDEX);
+
+ div = FIELD_GET(CS2600_BCLK_DIV_MASK, out_cfg1);
+ KUNIT_EXPECT_EQ(test, div, CS2600_KUNIT_BCLK_DIV_INDEX);
+}
+
+/* Register values after a soft reset */
+static const struct reg_sequence cs2600_kunit_regmap_reset_defaults[] = {
+ REG_SEQ0(CS2600_PLL_CFG1, 0x0080),
+ REG_SEQ0(CS2600_PLL_CFG2, 0x0008),
+ REG_SEQ0(CS2600_RATIO1_1, 0x0000),
+ REG_SEQ0(CS2600_RATIO1_2, 0x0000),
+ REG_SEQ0(CS2600_RATIO2_1, 0x0000),
+ REG_SEQ0(CS2600_RATIO2_2, 0x0000),
+ REG_SEQ0(CS2600_PLL_CFG3, 0x0000),
+ REG_SEQ0(CS2600_OUTPUT_CFG1, 0x0000),
+ REG_SEQ0(CS2600_OUTPUT_CFG2, 0x0000),
+ REG_SEQ0(CS2600_PHASE_ALIGNMENT_CFG1, 0x0000),
+ REG_SEQ0(CS2600_UNLOCK_INDICATORS, 0x0000),
+ REG_SEQ0(CS2600_ERROR_STS, 0x0000),
+};
+
+static int cs2600_kunit_regmap_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+ /* This should never be called */
+ return -EIO;
+}
+
+static int cs2600_kunit_regmap_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+ /* This should never be called */
+ return -EIO;
+}
+
+static unsigned int cs2600_kunit_i2c_read_unlock_indicators(struct cs2600_kunit_priv *priv)
+{
+ /* Fake the unlock status based on the PLL enable status */
+ if (!priv->force_unlock_status &&
+ regmap_test_bits(priv->regmap, CS2600_PLL_CFG1, CS2600_PLL_EN1) &&
+ regmap_test_bits(priv->regmap, CS2600_PLL_CFG2, CS2600_PLL_EN2))
+ return 0;
+
+ return CS2600_P_UNLOCK | CS2600_P_UNLOCK_STICKY |
+ CS2600_F_UNLOCK | CS2600_F_UNLOCK_STICKY;
+}
+
+/* Return mask with a 0 for any frozen bits in the addressed register */
+static unsigned int cs2600_kunit_i2c_reg_get_freeze_mask(struct cs2600_kunit_priv *priv,
+ unsigned int addr)
+{
+ if (!regmap_test_bits(priv->regmap, CS2600_PLL_CFG2, CS2600_FREEZE_EN))
+ return 0xffff;
+
+ switch (addr) {
+ case CS2600_PLL_CFG1:
+ return 0; /* whole register is blocked by FREEZE_EN */
+ case CS2600_PLL_CFG2:
+ return ~(u16)(CS2600_M_RATIO_SEL_MASK | CS2600_PLL_MODE_SEL);
+ case CS2600_OUTPUT_CFG1:
+ return ~(u16)(CS2600_BCLK_OUT_DIS | CS2600_FSYNC_OUT_DIS);
+ case CS2600_OUTPUT_CFG2:
+ return ~(u16)CS2600_AUX1OUT_SEL;
+ default:
+ return 0xffff;
+ }
+}
+
+static int cs2600_kunit_i2c_write(struct cs2600_kunit_priv *priv, unsigned int addr,
+ unsigned int val)
+{
+ unsigned int write_mask;
+
+ switch (addr) {
+ case CS2600_SW_RESET:
+ if (val != 0x5a)
+ return 0;
+
+ return regmap_multi_reg_write(priv->regmap,
+ cs2600_kunit_regmap_reset_defaults,
+ ARRAY_SIZE(cs2600_kunit_regmap_reset_defaults));
+ break;
+ default:
+ write_mask = cs2600_kunit_i2c_reg_get_freeze_mask(priv, addr);
+ return regmap_update_bits(priv->regmap, addr, write_mask, val);
+ }
+}
+
+static int cs2600_kunit_i2c_read(struct cs2600_kunit_priv *priv, unsigned int addr,
+ unsigned int *val)
+{
+ switch (addr) {
+ case CS2600_UNLOCK_INDICATORS:
+ *val = cs2600_kunit_i2c_read_unlock_indicators(priv);
+ return 0;
+ default:
+ return regmap_read(priv->regmap, addr, val);
+ }
+}
+
+static int cs2600_kunit_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+ struct cs2600_kunit_priv *priv = i2c_get_adapdata(adap);
+ unsigned int addr, val;
+ __be16 *bebuf;
+ int ret;
+
+ /* First word of first message is always the register address */
+ bebuf = (__force __be16 *)msgs[0].buf;
+ addr = be16_to_cpu(bebuf[0]);
+
+ /* A second word in the first message is a data write */
+ if (msgs[0].len >= 2 * sizeof(__be16)) {
+ val = be16_to_cpu(bebuf[1]);
+ ret = cs2600_kunit_i2c_write(priv, addr, val);
+ if (ret < 0)
+ return ret;
+
+ return 1;
+ }
+
+ /* If there is a second packet it is a read */
+ if (num == 2) {
+ ret = cs2600_kunit_i2c_read(priv, addr, &val);
+ if (ret < 0)
+ return ret;
+
+ bebuf = (__force __be16 *)msgs[1].buf;
+ bebuf[0] = cpu_to_be16(val);
+
+ return 2;
+ }
+
+ return 0;
+}
+
+static u32 cs2600_kunit_i2c_functionality(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C;
+}
+
+static const struct regmap_config cs2600_kunit_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 16,
+ .max_register = CS2600_MAX_REGISTER,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static const struct regmap_bus cs2600_kunit_regmap_bus = {
+ .reg_read = cs2600_kunit_regmap_reg_read,
+ .reg_write = cs2600_kunit_regmap_reg_write,
+};
+
+static const struct i2c_algorithm cs2600_kunit_i2c_algo = {
+ .xfer = cs2600_kunit_i2c_xfer,
+ .functionality = cs2600_kunit_i2c_functionality,
+};
+
+static int cs2600_kunit_i2c_driver_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct cs2600_kunit_priv *priv = container_of(to_platform_driver(dev->driver),
+ struct cs2600_kunit_priv,
+ i2c_pdrv);
+ int ret;
+
+ /* Create emulated chip register map */
+ priv->regmap = devm_regmap_init(dev, &cs2600_kunit_regmap_bus, priv,
+ &cs2600_kunit_regmap_config);
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+
+ regcache_cache_only(priv->regmap, true);
+
+ /*
+ * The only known value is the device ID until the driver
+ * issues a chip reset.
+ */
+ regmap_write(priv->regmap, CS2600_DEVICE_ID1, 0x2600);
+ regmap_write(priv->regmap, CS2600_DEVICE_ID2, 0xb0);
+
+ strscpy(priv->adapter.name, pdev->name, sizeof(priv->adapter.name));
+ priv->adapter.owner = THIS_MODULE;
+ priv->adapter.algo = &cs2600_kunit_i2c_algo;
+ priv->adapter.dev.parent = dev;
+ priv->adapter.dev.of_node = dev->of_node;
+ i2c_set_adapdata(&priv->adapter, priv);
+
+ ret = devm_i2c_add_adapter(dev, &priv->adapter);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add I2C adapter\n");
+
+ return 0;
+}
+
+static int cs2600_kunit_refclk_driver_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct cs2600_kunit_priv *priv = container_of(to_platform_driver(dev->driver),
+ struct cs2600_kunit_priv,
+ refclk_pdrv);
+ int ret;
+
+ priv->refclk_hw = devm_clk_hw_register_fixed_rate(dev, "cs2600_kunit_refclk",
+ NULL, 0,
+ priv->refclk_rate);
+ if (IS_ERR_OR_NULL(priv->refclk_hw))
+ return dev_err_probe(dev, PTR_ERR(priv->refclk_hw), "Failed to register refclk\n");
+
+ ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, priv->refclk_hw);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register provider\n");
+
+ return 0;
+}
+
+static int cs2600_kunit_clkin_driver_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct cs2600_kunit_priv *priv = container_of(to_platform_driver(dev->driver),
+ struct cs2600_kunit_priv,
+ clkin_pdrv);
+ int ret;
+
+ priv->clkin_hw = devm_clk_hw_register_fixed_rate(dev, "cs2600_kunit_clkin",
+ NULL, 0,
+ priv->clkin_rate);
+ if (IS_ERR_OR_NULL(priv->clkin_hw))
+ return dev_err_probe(dev, PTR_ERR(priv->clkin_hw), "Failed to register clkin\n");
+
+ ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, priv->clkin_hw);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register provider\n");
+
+ return 0;
+}
+
+static void cs2600_kunit_clk_consumer_driver_remove(struct platform_device *pdev)
+{
+}
+
+static int cs2600_kunit_clk_consumer_driver_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct cs2600_kunit_priv *priv = container_of(to_platform_driver(dev->driver),
+ struct cs2600_kunit_priv,
+ consumer_pdrv);
+ struct clk_bulk_data *clocks;
+ int ret;
+
+ clocks = devm_kcalloc(dev, CS2600_FSYNC_OUT + 1, sizeof(*clocks), GFP_KERNEL);
+ if (!clocks)
+ return -ENOMEM;
+
+ clocks[CS2600_PLL_OUT].id = "pllout";
+ clocks[CS2600_CLK_OUT].id = "clkout";
+ clocks[CS2600_BCLK_OUT].id = "bclk";
+ clocks[CS2600_FSYNC_OUT].id = "fsync";
+
+ ret = devm_clk_bulk_get(dev, CS2600_FSYNC_OUT + 1, clocks);
+ if (ret)
+ return dev_err_probe(dev, ret, "Could not get clocks\n");
+
+ priv->pllout = clocks[CS2600_PLL_OUT].clk;
+ priv->clkout = clocks[CS2600_CLK_OUT].clk;
+ priv->bclk = clocks[CS2600_BCLK_OUT].clk;
+ priv->fsync = clocks[CS2600_FSYNC_OUT].clk;
+
+ complete(&priv->consumers_completion);
+
+ return 0;
+}
+
+static const struct of_device_id cs2600_kunit_i2c_driver_of_match[] = {
+ { .compatible = "cirrus,cs2600-kunit-i2c", },
+ {}
+};
+
+static const struct of_device_id cs2600_kunit_refclk_driver_of_match[] = {
+ { .compatible = "cirrus,cs2600-kunit-refclk", },
+ {}
+};
+
+static const struct of_device_id cs2600_kunit_clkin_driver_of_match[] = {
+ { .compatible = "cirrus,cs2600-kunit-clkin", },
+ {}
+};
+
+static const struct of_device_id cs2600_kunit_clk_consumer_driver_of_match[] = {
+ { .compatible = "cirrus,cs2600-kunit-consumer", (void *)(uintptr_t)0 },
+ {}
+};
+
+static int cs2600_kunit_case_common_init(struct kunit *test)
+{
+ const struct cs2600_kunit_params *params = test->param_value;
+ struct cs2600_kunit_priv *priv;
+ struct device_node *np;
+
+ priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ test->priv = priv;
+ priv->test = test;
+ init_completion(&priv->consumers_completion);
+
+ if (params) {
+ priv->refclk_rate = params->ref_clk;
+ priv->clkin_rate = params->clk_in;
+ }
+
+ if (priv->refclk_rate == 0)
+ priv->refclk_rate = CS2600_KUNIT_DEFAULT_REFCLK_RATE;
+
+ /* Create dummy I2C bus driver */
+ priv->i2c_pdrv.probe = cs2600_kunit_i2c_driver_probe;
+ priv->i2c_pdrv.driver.of_match_table = cs2600_kunit_i2c_driver_of_match;
+ priv->i2c_pdrv.driver.name = "cs2600_kunit_i2c_driver";
+ priv->i2c_pdrv.driver.owner = THIS_MODULE;
+ KUNIT_ASSERT_EQ(test, 0, kunit_platform_driver_register(test, &priv->i2c_pdrv));
+
+ /* Create dummy refclk driver */
+ priv->refclk_pdrv.probe = cs2600_kunit_refclk_driver_probe;
+ priv->refclk_pdrv.driver.of_match_table = cs2600_kunit_refclk_driver_of_match;
+ priv->refclk_pdrv.driver.name = "cs2600_kunit_refclk_driver";
+ priv->refclk_pdrv.driver.owner = THIS_MODULE;
+ KUNIT_ASSERT_EQ(test, 0, kunit_platform_driver_register(test, &priv->refclk_pdrv));
+
+ /* Create dummy clkin driver */
+ priv->clkin_pdrv.probe = cs2600_kunit_clkin_driver_probe;
+ priv->clkin_pdrv.driver.of_match_table = cs2600_kunit_clkin_driver_of_match;
+ priv->clkin_pdrv.driver.name = "cs2600_kunit_clkin_driver";
+ priv->clkin_pdrv.driver.owner = THIS_MODULE;
+ KUNIT_ASSERT_EQ(test, 0, kunit_platform_driver_register(test, &priv->clkin_pdrv));
+
+ /* Create dummy consumers */
+ priv->consumer_pdrv.probe = cs2600_kunit_clk_consumer_driver_probe;
+ priv->consumer_pdrv.remove = cs2600_kunit_clk_consumer_driver_remove;
+ priv->consumer_pdrv.driver.of_match_table = cs2600_kunit_clk_consumer_driver_of_match;
+ priv->consumer_pdrv.driver.name = "cs2600_kunit_consumer_driver";
+ priv->consumer_pdrv.driver.owner = THIS_MODULE;
+ KUNIT_ASSERT_EQ(test, 0, kunit_platform_driver_register(test, &priv->consumer_pdrv));
+
+ /* Wait for probes if the test case init already loaded the overlay */
+ np = of_find_node_by_name(NULL, "cs2600-test-consumer");
+ of_node_put(np);
+ if (!IS_ERR_OR_NULL(np))
+ cs2600_kunit_wait_for_probes(test);
+
+ return 0;
+}
+
+static int cs2600_kunit_case_manual_intosc_only_init(struct kunit *test)
+{
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_manual_intosc_only));
+
+ return cs2600_kunit_case_common_init(test);
+}
+
+static int cs2600_kunit_case_manual_refclk_only_init(struct kunit *test)
+{
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_manual_refclk_only));
+
+ return cs2600_kunit_case_common_init(test);
+}
+
+static int cs2600_kunit_case_manual_intosc_clkin_init(struct kunit *test)
+{
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_manual_intosc_clkin));
+
+ return cs2600_kunit_case_common_init(test);
+}
+
+static int cs2600_kunit_case_manual_refclk_clkin_init(struct kunit *test)
+{
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_manual_refclk_clkin));
+
+ return cs2600_kunit_case_common_init(test);
+}
+
+static int cs2600_kunit_case_smart_intosc_init(struct kunit *test)
+{
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_smart_intosc));
+
+ return cs2600_kunit_case_common_init(test);
+}
+
+static int cs2600_kunit_case_smart_refclk_init(struct kunit *test)
+{
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_smart_refclk));
+
+ return cs2600_kunit_case_common_init(test);
+}
+
+static int cs2600_kunit_case_smart_clkin_only_intosc_init(struct kunit *test)
+{
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_smart_clkin_only_intosc));
+
+ return cs2600_kunit_case_common_init(test);
+}
+
+static int cs2600_kunit_case_smart_clkin_only_refclk_init(struct kunit *test)
+{
+ KUNIT_ASSERT_EQ(test, 0,
+ of_overlay_apply_kunit(test, kunit_clk_cs2600_smart_clkin_only_refclk));
+
+ return cs2600_kunit_case_common_init(test);
+}
+
+static void cs2600_kunit_refclk_param_desc(const struct cs2600_kunit_params *param,
+ char *desc)
+{
+ snprintf(desc, KUNIT_PARAM_DESC_SIZE, "ref_clk:%u", param->ref_clk);
+}
+
+static void cs2600_kunit_clkin_param_desc(const struct cs2600_kunit_params *param,
+ char *desc)
+{
+ snprintf(desc, KUNIT_PARAM_DESC_SIZE, "clk_in:%u", param->clk_in);
+}
+
+static void cs2600_kunit_refclk_clkin_param_desc(const struct cs2600_kunit_params *param,
+ char *desc)
+{
+ snprintf(desc, KUNIT_PARAM_DESC_SIZE, "ref_clk:%u clk_in:%u",
+ param->ref_clk, param->clk_in);
+}
+
+static const struct cs2600_kunit_params cs2600_kunit_synth_mode_params[] = {
+ { .ref_clk = 8000000, .s_ratios = cs2600_kunit_8M_ratios },
+ { .ref_clk = 12000000, .s_ratios = cs2600_kunit_12M_ratios },
+ { .ref_clk = 16000000, .s_ratios = cs2600_kunit_16M_ratios },
+ { .ref_clk = 19200000, .s_ratios = cs2600_kunit_19_2M_ratios },
+ { .ref_clk = 24576000, .s_ratios = cs2600_kunit_24_576M_ratios },
+ { .ref_clk = 25000000, .s_ratios = cs2600_kunit_25M_ratios },
+ { .ref_clk = 27000000, .s_ratios = cs2600_kunit_27M_ratios },
+ { .ref_clk = 75000000, .s_ratios = cs2600_kunit_75M_ratios },
+};
+
+
+KUNIT_ARRAY_PARAM(cs2600_kunit_synth_mode, cs2600_kunit_synth_mode_params,
+ cs2600_kunit_refclk_param_desc);
+
+/* Internal oscillator is fixed at 12.0 MHz */
+static const struct cs2600_kunit_params cs2600_kunit_synth_mode_intosc_params[] = {
+ { .ref_clk = 12000000, .s_ratios = cs2600_kunit_12M_ratios },
+};
+
+KUNIT_ARRAY_PARAM(cs2600_kunit_synth_mode_intosc, cs2600_kunit_synth_mode_intosc_params,
+ cs2600_kunit_refclk_param_desc);
+
+static const struct cs2600_kunit_params cs2600_kunit_refclk_div_params[] = {
+ /* Limits of each range */
+ { .ref_clk = 8000000 },
+ { .ref_clk = 18750000 },
+ { .ref_clk = 16000000 },
+ { .ref_clk = 37500000 },
+ { .ref_clk = 32000000 },
+ { .ref_clk = 75000000 },
+
+ /* Some intermediate values */
+ { .ref_clk = 12000000 },
+ { .ref_clk = 16000000 },
+ { .ref_clk = 24576000 },
+ { .ref_clk = 27000000 },
+ { .ref_clk = 48000000 },
+ { .ref_clk = 64000000 },
+};
+
+KUNIT_ARRAY_PARAM(cs2600_kunit_refclk_div, cs2600_kunit_refclk_div_params,
+ cs2600_kunit_refclk_param_desc);
+
+static const struct cs2600_kunit_params cs2600_kunit_mult_mode_params[] = {
+ { .clk_in = 50, .m_ratios = cs2600_kunit_50_ratios },
+ { .clk_in = 2000, .m_ratios = cs2600_kunit_2k_ratios },
+ { .clk_in = 48000, .m_ratios = cs2600_kunit_48k_ratios },
+ { .clk_in = 1411200, .m_ratios = cs2600_kunit_1_4112M_ratios },
+ { .clk_in = 8000000, .m_ratios = cs2600_kunit_8M_ratios },
+ { .clk_in = 12000000, .m_ratios = cs2600_kunit_12M_ratios },
+ { .clk_in = 16000000, .m_ratios = cs2600_kunit_16M_ratios },
+ { .clk_in = 19200000, .m_ratios = cs2600_kunit_19_2M_ratios },
+ { .clk_in = 24576000, .m_ratios = cs2600_kunit_24_576M_ratios },
+ { .clk_in = 25000000, .m_ratios = cs2600_kunit_25M_ratios },
+ { .clk_in = 27000000, .m_ratios = cs2600_kunit_27M_ratios },
+ { .clk_in = 30000000, .m_ratios = cs2600_kunit_30M_ratios },
+};
+
+KUNIT_ARRAY_PARAM(cs2600_kunit_mult_mode, cs2600_kunit_mult_mode_params,
+ cs2600_kunit_clkin_param_desc);
+
+/* Subset of dual-input params with 12MHz refclk the only rate supported by intosc */
+static const struct cs2600_kunit_params cs2600_kunit_12Msynth_mult_params[] = {
+ { .ref_clk = 12000000, .s_ratios = cs2600_kunit_12M_ratios,
+ .clk_in = 50, .m_ratios = cs2600_kunit_50_ratios,
+ },
+ { .ref_clk = 12000000, .s_ratios = cs2600_kunit_12M_ratios,
+ .clk_in = 2000, .m_ratios = cs2600_kunit_2k_ratios,
+ },
+ { .ref_clk = 12000000, .s_ratios = cs2600_kunit_12M_ratios,
+ .clk_in = 48000, .m_ratios = cs2600_kunit_48k_ratios,
+ },
+ { .ref_clk = 12000000, .s_ratios = cs2600_kunit_12M_ratios,
+ .clk_in = 1411200, .m_ratios = cs2600_kunit_1_4112M_ratios,
+ },
+ { .ref_clk = 12000000, .s_ratios = cs2600_kunit_12M_ratios,
+ .clk_in = 8000000, .m_ratios = cs2600_kunit_8M_ratios,
+ },
+ { .ref_clk = 12000000, .s_ratios = cs2600_kunit_12M_ratios,
+ .clk_in = 12000000, .m_ratios = cs2600_kunit_12M_ratios,
+ },
+ { .ref_clk = 12000000, .s_ratios = cs2600_kunit_12M_ratios,
+ .clk_in = 16000000, .m_ratios = cs2600_kunit_16M_ratios,
+ },
+ { .ref_clk = 12000000, .s_ratios = cs2600_kunit_12M_ratios,
+ .clk_in = 19200000, .m_ratios = cs2600_kunit_19_2M_ratios,
+ },
+ { .ref_clk = 12000000, .s_ratios = cs2600_kunit_12M_ratios,
+ .clk_in = 24576000, .m_ratios = cs2600_kunit_24_576M_ratios,
+ },
+ { .ref_clk = 12000000, .s_ratios = cs2600_kunit_12M_ratios,
+ .clk_in = 30000000, .m_ratios = cs2600_kunit_30M_ratios,
+ },
+};
+
+KUNIT_ARRAY_PARAM(cs2600_kunit_12Msynth_mult, cs2600_kunit_12Msynth_mult_params,
+ cs2600_kunit_refclk_clkin_param_desc);
+
+/*
+ * Subset of dual-input params with clk_in frequencies < 8 MHz.
+ * These are only allowed in manual multiplier mode
+ */
+static const struct cs2600_kunit_params cs2600_kunit_synth_mult_low_clkin_params[] = {
+ { .ref_clk = 8000000, .s_ratios = cs2600_kunit_8M_ratios,
+ .clk_in = 50, .m_ratios = cs2600_kunit_50_ratios,
+ },
+ { .ref_clk = 8000000, .s_ratios = cs2600_kunit_8M_ratios,
+ .clk_in = 2000, .m_ratios = cs2600_kunit_2k_ratios,
+ },
+ { .ref_clk = 8000000, .s_ratios = cs2600_kunit_8M_ratios,
+ .clk_in = 48000, .m_ratios = cs2600_kunit_48k_ratios,
+ },
+ { .ref_clk = 8000000, .s_ratios = cs2600_kunit_8M_ratios,
+ .clk_in = 1411200, .m_ratios = cs2600_kunit_1_4112M_ratios,
+ },
+
+ { .ref_clk = 24576000, .s_ratios = cs2600_kunit_24_576M_ratios,
+ .clk_in = 50, .m_ratios = cs2600_kunit_50_ratios,
+ },
+ { .ref_clk = 24576000, .s_ratios = cs2600_kunit_24_576M_ratios,
+ .clk_in = 2000, .m_ratios = cs2600_kunit_2k_ratios,
+ },
+ { .ref_clk = 24576000, .s_ratios = cs2600_kunit_24_576M_ratios,
+ .clk_in = 48000, .m_ratios = cs2600_kunit_48k_ratios,
+ },
+ { .ref_clk = 24576000, .s_ratios = cs2600_kunit_24_576M_ratios,
+ .clk_in = 1411200, .m_ratios = cs2600_kunit_1_4112M_ratios,
+ },
+
+ { .ref_clk = 75000000, .s_ratios = cs2600_kunit_75M_ratios,
+ .clk_in = 50, .m_ratios = cs2600_kunit_50_ratios,
+ },
+ { .ref_clk = 75000000, .s_ratios = cs2600_kunit_75M_ratios,
+ .clk_in = 2000, .m_ratios = cs2600_kunit_2k_ratios,
+ },
+ { .ref_clk = 75000000, .s_ratios = cs2600_kunit_75M_ratios,
+ .clk_in = 48000, .m_ratios = cs2600_kunit_48k_ratios,
+ },
+ { .ref_clk = 75000000, .s_ratios = cs2600_kunit_75M_ratios,
+ .clk_in = 1411200, .m_ratios = cs2600_kunit_1_4112M_ratios,
+ },
+};
+
+KUNIT_ARRAY_PARAM(cs2600_kunit_synth_mult_low_clkin,
+ cs2600_kunit_synth_mult_low_clkin_params,
+ cs2600_kunit_refclk_clkin_param_desc);
+
+
+/* Subset of dual-input params with clk_in frequencies in range for smart mode */
+static const struct cs2600_kunit_params cs2600_kunit_synth_mult_params[] = {
+ { .ref_clk = 8000000, .s_ratios = cs2600_kunit_8M_ratios,
+ .clk_in = 8000000, .m_ratios = cs2600_kunit_8M_ratios,
+ },
+ { .ref_clk = 8000000, .s_ratios = cs2600_kunit_8M_ratios,
+ .clk_in = 12000000, .m_ratios = cs2600_kunit_12M_ratios,
+ },
+ { .ref_clk = 8000000, .s_ratios = cs2600_kunit_8M_ratios,
+ .clk_in = 16000000, .m_ratios = cs2600_kunit_16M_ratios,
+ },
+ { .ref_clk = 8000000, .s_ratios = cs2600_kunit_8M_ratios,
+ .clk_in = 19200000, .m_ratios = cs2600_kunit_19_2M_ratios,
+ },
+ { .ref_clk = 8000000, .s_ratios = cs2600_kunit_8M_ratios,
+ .clk_in = 24576000, .m_ratios = cs2600_kunit_24_576M_ratios,
+ },
+ { .ref_clk = 8000000, .s_ratios = cs2600_kunit_8M_ratios,
+ .clk_in = 30000000, .m_ratios = cs2600_kunit_30M_ratios,
+ },
+
+ { .ref_clk = 24576000, .s_ratios = cs2600_kunit_24_576M_ratios,
+ .clk_in = 8000000, .m_ratios = cs2600_kunit_8M_ratios,
+ },
+ { .ref_clk = 24576000, .s_ratios = cs2600_kunit_24_576M_ratios,
+ .clk_in = 12000000, .m_ratios = cs2600_kunit_12M_ratios,
+ },
+ { .ref_clk = 24576000, .s_ratios = cs2600_kunit_24_576M_ratios,
+ .clk_in = 16000000, .m_ratios = cs2600_kunit_16M_ratios,
+ },
+ { .ref_clk = 24576000, .s_ratios = cs2600_kunit_24_576M_ratios,
+ .clk_in = 19200000, .m_ratios = cs2600_kunit_19_2M_ratios,
+ },
+ { .ref_clk = 24576000, .s_ratios = cs2600_kunit_24_576M_ratios,
+ .clk_in = 24576000, .m_ratios = cs2600_kunit_24_576M_ratios,
+ },
+ { .ref_clk = 24576000, .s_ratios = cs2600_kunit_24_576M_ratios,
+ .clk_in = 30000000, .m_ratios = cs2600_kunit_30M_ratios,
+ },
+
+ { .ref_clk = 75000000, .s_ratios = cs2600_kunit_75M_ratios,
+ .clk_in = 8000000, .m_ratios = cs2600_kunit_8M_ratios,
+ },
+ { .ref_clk = 75000000, .s_ratios = cs2600_kunit_75M_ratios,
+ .clk_in = 12000000, .m_ratios = cs2600_kunit_12M_ratios,
+ },
+ { .ref_clk = 75000000, .s_ratios = cs2600_kunit_75M_ratios,
+ .clk_in = 16000000, .m_ratios = cs2600_kunit_16M_ratios,
+ },
+ { .ref_clk = 75000000, .s_ratios = cs2600_kunit_75M_ratios,
+ .clk_in = 19200000, .m_ratios = cs2600_kunit_19_2M_ratios,
+ },
+ { .ref_clk = 75000000, .s_ratios = cs2600_kunit_75M_ratios,
+ .clk_in = 24576000, .m_ratios = cs2600_kunit_24_576M_ratios,
+ },
+ { .ref_clk = 75000000, .s_ratios = cs2600_kunit_75M_ratios,
+ .clk_in = 30000000, .m_ratios = cs2600_kunit_30M_ratios,
+ },
+};
+
+KUNIT_ARRAY_PARAM(cs2600_kunit_synth_mult, cs2600_kunit_synth_mult_params,
+ cs2600_kunit_refclk_clkin_param_desc);
+
+static const struct cs2600_kunit_params cs2600_kunit_assigned_params[] = {
+ { .ref_clk = 49152000 },
+};
+
+KUNIT_ARRAY_PARAM(cs2600_kunit_assigned, cs2600_kunit_assigned_params,
+ cs2600_kunit_refclk_param_desc);
+
+#define CS2600_KUNIT_RATIO_CALC_SPEED_ATTR { .speed = KUNIT_SPEED_VERY_SLOW, }
+
+static struct kunit_case cs2600_kunit_manual_intosc_only_cases[] = {
+ KUNIT_CASE(cs2600_kunit_clock_out_initially_disabled),
+ KUNIT_CASE(cs2600_kunit_aux1_out_disabled),
+ KUNIT_CASE(cs2600_kunit_sysclk_source_is_internal),
+ KUNIT_CASE(cs2600_kunit_parent_tree),
+ KUNIT_CASE(cs2600_kunit_single_refclk_parent),
+ KUNIT_CASE_PARAM_ATTR(cs2600_kunit_synth_ratio,
+ cs2600_kunit_synth_mode_intosc_gen_params,
+ CS2600_KUNIT_RATIO_CALC_SPEED_ATTR),
+ KUNIT_CASE(cs2600_kunit_pll_enable_disable),
+ KUNIT_CASE(cs2600_kunit_pll_no_lock),
+ KUNIT_CASE(cs2600_kunit_clk_out_enable_disable),
+
+ { /* terminator */ }
+};
+
+static struct kunit_case cs2600_kunit_manual_refclk_only_cases[] = {
+ KUNIT_CASE(cs2600_kunit_clock_out_initially_disabled),
+ KUNIT_CASE(cs2600_kunit_aux1_out_disabled),
+ KUNIT_CASE(cs2600_kunit_sysclk_source_is_ref_clk_in),
+ KUNIT_CASE(cs2600_kunit_parent_tree),
+ KUNIT_CASE(cs2600_kunit_single_refclk_parent),
+ KUNIT_CASE_PARAM_ATTR(cs2600_kunit_ref_clk_divider,
+ cs2600_kunit_refclk_div_gen_params,
+ CS2600_KUNIT_RATIO_CALC_SPEED_ATTR),
+ KUNIT_CASE_PARAM_ATTR(cs2600_kunit_synth_ratio,
+ cs2600_kunit_synth_mode_gen_params,
+ CS2600_KUNIT_RATIO_CALC_SPEED_ATTR),
+
+ KUNIT_CASE(cs2600_kunit_pll_enable_disable),
+ KUNIT_CASE(cs2600_kunit_clk_out_enable_disable),
+
+ { /* terminator */ }
+};
+
+static struct kunit_case cs2600_kunit_manual_intosc_clkin_cases[] = {
+ KUNIT_CASE(cs2600_kunit_clock_out_initially_disabled),
+ KUNIT_CASE(cs2600_kunit_aux1_out_disabled),
+ KUNIT_CASE(cs2600_kunit_sysclk_source_is_internal),
+ KUNIT_CASE(cs2600_kunit_parent_tree),
+ KUNIT_CASE(cs2600_kunit_intosc_clkin_parents),
+
+ /* Internal oscillator can be used for synth mode */
+ KUNIT_CASE_PARAM_ATTR(cs2600_kunit_synth_ratio,
+ cs2600_kunit_synth_mode_intosc_gen_params,
+ CS2600_KUNIT_RATIO_CALC_SPEED_ATTR),
+
+ KUNIT_CASE_PARAM_ATTR(cs2600_kunit_mult_ratio,
+ cs2600_kunit_mult_mode_gen_params,
+ CS2600_KUNIT_RATIO_CALC_SPEED_ATTR),
+ KUNIT_CASE_PARAM_ATTR(cs2600_kunit_change_parent_same_clkout,
+ cs2600_kunit_12Msynth_mult_gen_params,
+ CS2600_KUNIT_RATIO_CALC_SPEED_ATTR),
+
+ KUNIT_CASE(cs2600_kunit_pll_enable_disable),
+ KUNIT_CASE(cs2600_kunit_clk_out_enable_disable),
+
+ { /* terminator */ }
+};
+
+static struct kunit_case cs2600_kunit_manual_refclk_clkin_cases[] = {
+ KUNIT_CASE(cs2600_kunit_clock_out_initially_disabled),
+ KUNIT_CASE(cs2600_kunit_aux1_out_disabled),
+ KUNIT_CASE(cs2600_kunit_sysclk_source_is_ref_clk_in),
+ KUNIT_CASE(cs2600_kunit_parent_tree),
+ KUNIT_CASE(cs2600_kunit_refclk_clkin_parents),
+ KUNIT_CASE_PARAM(cs2600_kunit_ref_clk_divider, cs2600_kunit_refclk_div_gen_params),
+
+ KUNIT_CASE_PARAM_ATTR(cs2600_kunit_synth_ratio,
+ cs2600_kunit_synth_mode_gen_params,
+ CS2600_KUNIT_RATIO_CALC_SPEED_ATTR),
+
+ KUNIT_CASE_PARAM_ATTR(cs2600_kunit_mult_ratio,
+ cs2600_kunit_mult_mode_gen_params,
+ CS2600_KUNIT_RATIO_CALC_SPEED_ATTR),
+ KUNIT_CASE_PARAM_ATTR(cs2600_kunit_change_parent_same_clkout,
+ cs2600_kunit_12Msynth_mult_gen_params,
+ CS2600_KUNIT_RATIO_CALC_SPEED_ATTR),
+ KUNIT_CASE_PARAM_ATTR(cs2600_kunit_change_parent_same_clkout,
+ cs2600_kunit_synth_mult_low_clkin_gen_params,
+ CS2600_KUNIT_RATIO_CALC_SPEED_ATTR),
+ KUNIT_CASE_PARAM_ATTR(cs2600_kunit_change_parent_same_clkout,
+ cs2600_kunit_synth_mult_gen_params,
+ CS2600_KUNIT_RATIO_CALC_SPEED_ATTR),
+
+ KUNIT_CASE(cs2600_kunit_pll_enable_disable),
+ KUNIT_CASE(cs2600_kunit_clk_out_enable_disable),
+
+ { /* terminator */ }
+};
+
+static struct kunit_case cs2600_kunit_smart_intosc_cases[] = {
+ KUNIT_CASE(cs2600_kunit_clock_out_initially_disabled),
+ KUNIT_CASE(cs2600_kunit_aux1_out_disabled),
+ KUNIT_CASE(cs2600_kunit_sysclk_source_is_internal),
+ KUNIT_CASE(cs2600_kunit_parent_tree),
+ KUNIT_CASE(cs2600_kunit_smart_parents),
+
+ KUNIT_CASE_PARAM_ATTR(cs2600_kunit_smart_ratio,
+ cs2600_kunit_12Msynth_mult_gen_params,
+ CS2600_KUNIT_RATIO_CALC_SPEED_ATTR),
+
+ KUNIT_CASE(cs2600_kunit_pll_enable_disable),
+ KUNIT_CASE(cs2600_kunit_clk_out_enable_disable),
+
+ { /* terminator */ }
+};
+
+static struct kunit_case cs2600_kunit_smart_clkin_only_intosc_cases[] = {
+ KUNIT_CASE(cs2600_kunit_clock_out_initially_disabled),
+ KUNIT_CASE(cs2600_kunit_aux1_out_disabled),
+ KUNIT_CASE(cs2600_kunit_sysclk_source_is_internal),
+ KUNIT_CASE(cs2600_kunit_parent_tree),
+ KUNIT_CASE(cs2600_kunit_smart_parents),
+
+ KUNIT_CASE_PARAM_ATTR(cs2600_kunit_smart_clkin_only_ratio,
+ cs2600_kunit_12Msynth_mult_gen_params,
+ CS2600_KUNIT_RATIO_CALC_SPEED_ATTR),
+
+ KUNIT_CASE(cs2600_kunit_pll_enable_disable),
+ KUNIT_CASE(cs2600_kunit_clk_out_enable_disable),
+
+ { /* terminator */ }
+};
+
+static struct kunit_case cs2600_kunit_smart_refclk_cases[] = {
+ KUNIT_CASE(cs2600_kunit_clock_out_initially_disabled),
+ KUNIT_CASE(cs2600_kunit_aux1_out_disabled),
+ KUNIT_CASE(cs2600_kunit_sysclk_source_is_ref_clk_in),
+ KUNIT_CASE(cs2600_kunit_parent_tree),
+ KUNIT_CASE(cs2600_kunit_smart_parents),
+
+ KUNIT_CASE_PARAM_ATTR(cs2600_kunit_smart_ratio,
+ cs2600_kunit_12Msynth_mult_gen_params,
+ CS2600_KUNIT_RATIO_CALC_SPEED_ATTR),
+ KUNIT_CASE_PARAM_ATTR(cs2600_kunit_smart_ratio,
+ cs2600_kunit_synth_mult_low_clkin_gen_params,
+ CS2600_KUNIT_RATIO_CALC_SPEED_ATTR),
+ KUNIT_CASE_PARAM_ATTR(cs2600_kunit_smart_ratio,
+ cs2600_kunit_synth_mult_gen_params,
+ CS2600_KUNIT_RATIO_CALC_SPEED_ATTR),
+
+ KUNIT_CASE(cs2600_kunit_pll_enable_disable),
+ KUNIT_CASE(cs2600_kunit_clk_out_enable_disable),
+
+ { /* terminator */ }
+};
+
+static struct kunit_case cs2600_kunit_smart_clkin_only_refclk_cases[] = {
+ KUNIT_CASE(cs2600_kunit_clock_out_initially_disabled),
+ KUNIT_CASE(cs2600_kunit_aux1_out_disabled),
+ KUNIT_CASE(cs2600_kunit_sysclk_source_is_ref_clk_in),
+ KUNIT_CASE(cs2600_kunit_parent_tree),
+ KUNIT_CASE(cs2600_kunit_smart_parents),
+
+ KUNIT_CASE_PARAM_ATTR(cs2600_kunit_smart_clkin_only_ratio,
+ cs2600_kunit_12Msynth_mult_gen_params,
+ CS2600_KUNIT_RATIO_CALC_SPEED_ATTR),
+ KUNIT_CASE_PARAM_ATTR(cs2600_kunit_smart_clkin_only_ratio,
+ cs2600_kunit_synth_mult_gen_params,
+ CS2600_KUNIT_RATIO_CALC_SPEED_ATTR),
+
+ KUNIT_CASE(cs2600_kunit_pll_enable_disable),
+ KUNIT_CASE(cs2600_kunit_clk_out_enable_disable),
+
+ { /* terminator */ }
+};
+
+/*
+ * The FSYNC and BCLK dividers do not depend on the PLL mode or reference clock
+ * types, so don't need to be re-tested for every PLL configuration.
+ *
+ * The simple synth mode from the internal oscillator is sufficient to provide
+ * a dummy reference clock config for the dividers.
+ */
+static struct kunit_case cs2600_kunit_bclk_fsync_divider_cases[] = {
+ KUNIT_CASE(cs2600_kunit_bclk_enable_disable),
+ KUNIT_CASE_PARAM_ATTR(cs2600_kunit_bclk_divider,
+ cs2600_kunit_synth_mode_intosc_gen_params,
+ CS2600_KUNIT_RATIO_CALC_SPEED_ATTR),
+ KUNIT_CASE(cs2600_kunit_fsync_enable_disable),
+ KUNIT_CASE_PARAM_ATTR(cs2600_kunit_fsync_divider,
+ cs2600_kunit_synth_mode_intosc_gen_params,
+ CS2600_KUNIT_RATIO_CALC_SPEED_ATTR),
+
+ { /* terminator */ }
+};
+
+static struct kunit_case cs2600_kunit_of_config_cases[] = {
+ KUNIT_CASE(cs2600_kunit_aux1_out_ref_clk_in),
+ KUNIT_CASE(cs2600_kunit_aux1_out_clk_in),
+ KUNIT_CASE(cs2600_kunit_aux1_out_freq_unlock),
+ KUNIT_CASE(cs2600_kunit_aux1_out_phase_unlock),
+ KUNIT_CASE(cs2600_kunit_aux1_out_clk_in_missing),
+ KUNIT_CASE(cs2600_kunit_name_output_clocks),
+ KUNIT_CASE(cs2600_kunit_bclk_fsync_not_invert),
+ KUNIT_CASE(cs2600_kunit_bclk_invert),
+ KUNIT_CASE(cs2600_kunit_fsync_invert),
+ KUNIT_CASE(cs2600_kunit_fsync_duty_50_50),
+ KUNIT_CASE(cs2600_kunit_fsync_duty_1),
+ KUNIT_CASE(cs2600_kunit_fsync_duty_2),
+ KUNIT_CASE(cs2600_kunit_fsync_duty_32),
+ KUNIT_CASE_PARAM(cs2600_kunit_assigned_clock_rates,
+ cs2600_kunit_assigned_gen_params),
+ { /* terminator */ }
+};
+
+/* No clock properties - only clock source is internal oscillator */
+static struct kunit_suite cs2600_kunit_manual_intosc_only_suite = {
+ .name = "clk_cs2600_kunit_manual_intosc_only",
+ .init = cs2600_kunit_case_manual_intosc_only_init,
+ .test_cases = cs2600_kunit_manual_intosc_only_cases,
+ .attr.speed = KUNIT_SPEED_SLOW,
+};
+
+/* Only clock property is ref_clk - only clock source is ref_clk */
+static struct kunit_suite cs2600_kunit_manual_refclk_only_suite = {
+ .name = "clk_cs2600_kunit_manual_refclk_only",
+ .init = cs2600_kunit_case_manual_refclk_only_init,
+ .test_cases = cs2600_kunit_manual_refclk_only_cases,
+ .attr.speed = KUNIT_SPEED_SLOW,
+};
+
+/* ref_clk and clk_in properties - ref_clk is internal oscillator */
+static struct kunit_suite cs2600_kunit_manual_intosc_clkin_suite = {
+ .name = "clk_cs2600_kunit_manual_clkin_only",
+ .init = cs2600_kunit_case_manual_intosc_clkin_init,
+ .test_cases = cs2600_kunit_manual_intosc_clkin_cases,
+ .attr.speed = KUNIT_SPEED_SLOW,
+};
+
+/* ref_clk and clk_in properties - both external clocks */
+static struct kunit_suite cs2600_kunit_manual_refclk_clkin_suite = {
+ .name = "clk_cs2600_kunit_manual_refclk_clkin",
+ .init = cs2600_kunit_case_manual_refclk_clkin_init,
+ .test_cases = cs2600_kunit_manual_refclk_clkin_cases,
+ .attr.speed = KUNIT_SPEED_SLOW,
+};
+
+/* Smart mode using internal oscillator as reference */
+static struct kunit_suite cs2600_kunit_smart_intosc_suite = {
+ .name = "clk_cs2600_kunit_smart_intosc",
+ .init = cs2600_kunit_case_smart_intosc_init,
+ .test_cases = cs2600_kunit_smart_intosc_cases,
+ .attr.speed = KUNIT_SPEED_SLOW,
+};
+
+/* Smart mode using external clock as reference */
+static struct kunit_suite cs2600_kunit_smart_refclk_suite = {
+ .name = "clk_cs2600_kunit_smart_refclk",
+ .init = cs2600_kunit_case_smart_refclk_init,
+ .test_cases = cs2600_kunit_smart_refclk_cases,
+ .attr.speed = KUNIT_SPEED_SLOW,
+};
+
+/* Smart clkin-only mode - reference is internal oscillator */
+static struct kunit_suite cs2600_kunit_smart_clkin_only_intosc_suite = {
+ .name = "clk_cs2600_kunit_smart_clkin_only_intosc",
+ .init = cs2600_kunit_case_smart_clkin_only_intosc_init,
+ .test_cases = cs2600_kunit_smart_clkin_only_intosc_cases,
+ .attr.speed = KUNIT_SPEED_SLOW,
+};
+
+/* Smart clkin-only mode - refclk is external source */
+static struct kunit_suite cs2600_kunit_smart_clkin_only_refclk_suite = {
+ .name = "clk_cs2600_kunit_smart_clkin_only_refclk",
+ .init = cs2600_kunit_case_smart_clkin_only_refclk_init,
+ .test_cases = cs2600_kunit_smart_clkin_only_refclk_cases,
+ .attr.speed = KUNIT_SPEED_SLOW,
+};
+
+/* Test BCLK and FSYNC dividers. Uses the manual intosc-only PLL config. */
+static struct kunit_suite cs2600_kunit_bclk_fsync_divider_suite = {
+ .name = "clk_cs2600_kunit_bclk_fsync_dividers",
+ .init = cs2600_kunit_case_manual_intosc_only_init,
+ .test_cases = cs2600_kunit_bclk_fsync_divider_cases,
+ .attr.speed = KUNIT_SPEED_SLOW,
+};
+
+/* Test that OF properties take effect */
+static struct kunit_suite cs2600_kunit_of_config_suite = {
+ .name = "clk_cs2600_kunit_of_config",
+ .init = cs2600_kunit_case_common_init,
+ .test_cases = cs2600_kunit_of_config_cases,
+ .attr.speed = KUNIT_SPEED_SLOW,
+};
+
+kunit_test_suites(&cs2600_kunit_manual_intosc_only_suite,
+ &cs2600_kunit_manual_refclk_only_suite,
+ &cs2600_kunit_manual_intosc_clkin_suite,
+ &cs2600_kunit_manual_refclk_clkin_suite,
+ &cs2600_kunit_smart_intosc_suite,
+ &cs2600_kunit_smart_refclk_suite,
+ &cs2600_kunit_smart_clkin_only_intosc_suite,
+ &cs2600_kunit_smart_clkin_only_refclk_suite,
+ &cs2600_kunit_bclk_fsync_divider_suite,
+ &cs2600_kunit_of_config_suite);
+
+MODULE_DESCRIPTION("KUnit test for the CS2600 clock driver");
+MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/cirrus/clk-cs2600-kunit.h b/drivers/clk/cirrus/clk-cs2600-kunit.h
new file mode 100644
index 000000000000..2387610cc23b
--- /dev/null
+++ b/drivers/clk/cirrus/clk-cs2600-kunit.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * KUnit test for the Cirrus Logic CS2600 clock driver.
+ *
+ * Copyright (C) 2026 Cirrus Logic, Inc. and
+ * Cirrus Logic International Semiconductor Ltd.
+ */
+
+#define CS2600_KUNIT_PLLOUT_RENAME "renamed_pll"
+#define CS2600_KUNIT_CLKOUT_RENAME "renamed_clk"
+#define CS2600_KUNIT_BCLK_RENAME "renamed_bs"
+#define CS2600_KUNIT_FSYNC_RENAME "renamed_fs"
+
+#define CS2600_KUNIT_CLKOUT_ASSIGNED 6144000
+#define CS2600_KUNIT_BCLK_ASSIGNED 1536000
+#define CS2600_KUNIT_FSYNC_ASSIGNED 96000
+#define CS2600_KUNIT_BCLK_DIV_INDEX 3
+#define CS2600_KUNIT_FSYNC_DIV_INDEX 2
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600.dtsi b/drivers/clk/cirrus/kunit_clk_cs2600.dtsi
new file mode 100644
index 000000000000..9fd11d322837
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600.dtsi
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/clock/cirrus,cs2600-clock.h>
+
+&{/} {
+ dummy_vreg: VDD-DUMMY {
+ compatible = "regulator-fixed";
+ regulator-name = "vdd";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-boot-on;
+ regulator-always-on;
+ };
+
+ cs2600_kunit_refclk: cs2600_kunit_refclk {
+ compatible = "cirrus,cs2600-kunit-refclk";
+ #clock-cells = <0>;
+ };
+
+ cs2600_kunit_clkin: cs2600_kunit_clkin {
+ compatible = "cirrus,cs2600-kunit-clkin";
+ #clock-cells = <0>;
+ };
+
+ cs2600_kunit_i2c: cs2600-kunit-i2c {
+ compatible = "cirrus,cs2600-kunit-i2c";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ clock-frequency = <400000>;
+ };
+
+ cs2600_test_consumer: cs2600-test-consumer {
+ compatible = "cirrus,cs2600-kunit-consumer";
+ clocks = <&cs2600 CS2600_PLL_OUT>, <&cs2600 CS2600_CLK_OUT>,
+ <&cs2600 CS2600_BCLK_OUT>, <&cs2600 CS2600_FSYNC_OUT>;
+ clock-names = "pllout", "clkout", "bclk", "fsync";
+ };
+};
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600_assigned.dtso b/drivers/clk/cirrus/kunit_clk_cs2600_assigned.dtso
new file mode 100644
index 000000000000..fd2d7b3b5b10
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600_assigned.dtso
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "kunit_clk_cs2600.dtsi"
+#include "clk-cs2600-kunit.h"
+
+&cs2600_kunit_i2c {
+ cs2600: cs2600@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&cs2600_kunit_refclk>;
+ clock-names = "ref_clk_in";
+ vdd-supply = <&dummy_vreg>;
+ };
+};
+
+&cs2600_test_consumer {
+ assigned-clocks = <&cs2600 CS2600_CLK_OUT>,
+ <&cs2600 CS2600_BCLK_OUT>,
+ <&cs2600 CS2600_FSYNC_OUT>;
+ assigned-clock-rates = <CS2600_KUNIT_CLKOUT_ASSIGNED
+ CS2600_KUNIT_BCLK_ASSIGNED
+ CS2600_KUNIT_FSYNC_ASSIGNED>;
+};
+
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_clk_in.dtso b/drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_clk_in.dtso
new file mode 100644
index 000000000000..aeaa3740d6fd
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_clk_in.dtso
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "kunit_clk_cs2600.dtsi"
+
+&cs2600_kunit_i2c {
+ cs2600: cs2600@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&cs2600_kunit_refclk>;
+ clock-names = "ref_clk_in";
+ vdd-supply = <&dummy_vreg>;
+ cirrus,aux1-output-source = "clk_in";
+ };
+};
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_clk_in_missing.dtso b/drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_clk_in_missing.dtso
new file mode 100644
index 000000000000..809e55f22d4d
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_clk_in_missing.dtso
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "kunit_clk_cs2600.dtsi"
+
+&cs2600_kunit_i2c {
+ cs2600: cs2600@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&cs2600_kunit_refclk>;
+ clock-names = "ref_clk_in";
+ vdd-supply = <&dummy_vreg>;
+ cirrus,aux1-output-source = "clkin_missing";
+ };
+};
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_freq_unlock.dtso b/drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_freq_unlock.dtso
new file mode 100644
index 000000000000..2d295792ef68
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_freq_unlock.dtso
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "kunit_clk_cs2600.dtsi"
+
+&cs2600_kunit_i2c {
+ cs2600: cs2600@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&cs2600_kunit_refclk>;
+ clock-names = "ref_clk_in";
+ vdd-supply = <&dummy_vreg>;
+ cirrus,aux1-output-source = "freq_unlock";
+ };
+};
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_phase_unlock.dtso b/drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_phase_unlock.dtso
new file mode 100644
index 000000000000..5c242cd06707
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_phase_unlock.dtso
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "kunit_clk_cs2600.dtsi"
+
+&cs2600_kunit_i2c {
+ cs2600: cs2600@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&cs2600_kunit_refclk>;
+ clock-names = "ref_clk_in";
+ vdd-supply = <&dummy_vreg>;
+ cirrus,aux1-output-source = "phase_unlock";
+ };
+};
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_ref_clk_in.dtso b/drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_ref_clk_in.dtso
new file mode 100644
index 000000000000..4e9205789ef4
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600_aux1_out_ref_clk_in.dtso
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "kunit_clk_cs2600.dtsi"
+
+&cs2600_kunit_i2c {
+ cs2600: cs2600@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&cs2600_kunit_refclk>;
+ clock-names = "ref_clk_in";
+ vdd-supply = <&dummy_vreg>;
+ cirrus,aux1-output-source = "ref_clk_in";
+ };
+};
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600_bclk_invert.dtso b/drivers/clk/cirrus/kunit_clk_cs2600_bclk_invert.dtso
new file mode 100644
index 000000000000..c3d89d7c8020
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600_bclk_invert.dtso
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "kunit_clk_cs2600.dtsi"
+
+&cs2600_kunit_i2c {
+ cs2600: cs2600@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&cs2600_kunit_refclk>;
+ clock-names = "ref_clk_in";
+ vdd-supply = <&dummy_vreg>;
+ cirrus,bclk-invert;
+ };
+};
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600_fsync_duty_1.dtso b/drivers/clk/cirrus/kunit_clk_cs2600_fsync_duty_1.dtso
new file mode 100644
index 000000000000..f3e175c93c65
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600_fsync_duty_1.dtso
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "kunit_clk_cs2600.dtsi"
+
+&cs2600_kunit_i2c {
+ cs2600: cs2600@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&cs2600_kunit_refclk>;
+ clock-names = "ref_clk_in";
+ vdd-supply = <&dummy_vreg>;
+ cirrus,fsync-duty-cycles = <1>;
+ };
+};
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600_fsync_duty_2.dtso b/drivers/clk/cirrus/kunit_clk_cs2600_fsync_duty_2.dtso
new file mode 100644
index 000000000000..11f308e49ff2
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600_fsync_duty_2.dtso
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "kunit_clk_cs2600.dtsi"
+
+&cs2600_kunit_i2c {
+ cs2600: cs2600@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&cs2600_kunit_refclk>;
+ clock-names = "ref_clk_in";
+ vdd-supply = <&dummy_vreg>;
+ cirrus,fsync-duty-cycles = <2>;
+ };
+};
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600_fsync_duty_32.dtso b/drivers/clk/cirrus/kunit_clk_cs2600_fsync_duty_32.dtso
new file mode 100644
index 000000000000..00656af862d7
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600_fsync_duty_32.dtso
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "kunit_clk_cs2600.dtsi"
+
+&cs2600_kunit_i2c {
+ cs2600: cs2600@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&cs2600_kunit_refclk>;
+ clock-names = "ref_clk_in";
+ vdd-supply = <&dummy_vreg>;
+ cirrus,fsync-duty-cycles = <32>;
+ };
+};
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600_fsync_invert.dtso b/drivers/clk/cirrus/kunit_clk_cs2600_fsync_invert.dtso
new file mode 100644
index 000000000000..d85b5d851266
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600_fsync_invert.dtso
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "kunit_clk_cs2600.dtsi"
+
+&cs2600_kunit_i2c {
+ cs2600: cs2600@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&cs2600_kunit_refclk>;
+ clock-names = "ref_clk_in";
+ vdd-supply = <&dummy_vreg>;
+ cirrus,fsync-invert;
+ };
+};
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600_manual_intosc_clkin.dtso b/drivers/clk/cirrus/kunit_clk_cs2600_manual_intosc_clkin.dtso
new file mode 100644
index 000000000000..859b28daf25c
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600_manual_intosc_clkin.dtso
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "kunit_clk_cs2600.dtsi"
+
+&cs2600_kunit_i2c {
+ cs2600: cs2600@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&cs2600_kunit_refclk>, <&cs2600_kunit_clkin>;
+ clock-names = "ref_clk_in", "clk_in";
+ cirrus,internal-oscillator;
+ vdd-supply = <&dummy_vreg>;
+ };
+};
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600_manual_intosc_only.dtso b/drivers/clk/cirrus/kunit_clk_cs2600_manual_intosc_only.dtso
new file mode 100644
index 000000000000..dba29f398774
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600_manual_intosc_only.dtso
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "kunit_clk_cs2600.dtsi"
+
+&cs2600_kunit_i2c {
+ cs2600: cs2600@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&cs2600_kunit_refclk>;
+ clock-names = "ref_clk_in";
+ cirrus,internal-oscillator;
+ vdd-supply = <&dummy_vreg>;
+ };
+};
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600_manual_refclk_clkin.dtso b/drivers/clk/cirrus/kunit_clk_cs2600_manual_refclk_clkin.dtso
new file mode 100644
index 000000000000..5af4eb45fee0
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600_manual_refclk_clkin.dtso
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "kunit_clk_cs2600.dtsi"
+
+&cs2600_kunit_i2c {
+ cs2600: cs2600@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&cs2600_kunit_refclk>, <&cs2600_kunit_clkin>;
+ clock-names = "ref_clk_in", "clk_in";
+ vdd-supply = <&dummy_vreg>;
+ };
+};
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600_manual_refclk_only.dtso b/drivers/clk/cirrus/kunit_clk_cs2600_manual_refclk_only.dtso
new file mode 100644
index 000000000000..dc2bf6f4a90a
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600_manual_refclk_only.dtso
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "kunit_clk_cs2600.dtsi"
+
+&cs2600_kunit_i2c {
+ cs2600: cs2600@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&cs2600_kunit_refclk>;
+ clock-names = "ref_clk_in";
+ vdd-supply = <&dummy_vreg>;
+ };
+};
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600_name_output_clocks.dtso b/drivers/clk/cirrus/kunit_clk_cs2600_name_output_clocks.dtso
new file mode 100644
index 000000000000..d05a69512bc6
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600_name_output_clocks.dtso
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "clk-cs2600-kunit.h"
+#include "kunit_clk_cs2600.dtsi"
+
+&cs2600_kunit_i2c {
+ cs2600: cs2600@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&cs2600_kunit_refclk>, <&cs2600_kunit_clkin>;
+ clock-names = "ref_clk_in", "clk_in";
+ vdd-supply = <&dummy_vreg>;
+
+ clock-output-names =
+ CS2600_KUNIT_PLLOUT_RENAME,
+ CS2600_KUNIT_CLKOUT_RENAME,
+ CS2600_KUNIT_BCLK_RENAME,
+ CS2600_KUNIT_FSYNC_RENAME;
+ };
+};
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600_smart_clkin_only_intosc.dtso b/drivers/clk/cirrus/kunit_clk_cs2600_smart_clkin_only_intosc.dtso
new file mode 100644
index 000000000000..76fe2e5a01c9
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600_smart_clkin_only_intosc.dtso
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "kunit_clk_cs2600.dtsi"
+
+&cs2600_kunit_i2c {
+ cs2600: cs2600@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&cs2600_kunit_refclk>, <&cs2600_kunit_clkin>;
+ clock-names = "ref_clk_in", "clk_in";
+ cirrus,internal-oscillator;
+ cirrus,smart-mode;
+ cirrus,smart-mode-clkin-only;
+ vdd-supply = <&dummy_vreg>;
+ };
+};
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600_smart_clkin_only_refclk.dtso b/drivers/clk/cirrus/kunit_clk_cs2600_smart_clkin_only_refclk.dtso
new file mode 100644
index 000000000000..83e1b57deeaa
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600_smart_clkin_only_refclk.dtso
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "kunit_clk_cs2600.dtsi"
+
+&cs2600_kunit_i2c {
+ cs2600: cs2600@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&cs2600_kunit_refclk>, <&cs2600_kunit_clkin>;
+ clock-names = "ref_clk_in", "clk_in";
+ cirrus,smart-mode;
+ cirrus,smart-mode-clkin-only;
+ vdd-supply = <&dummy_vreg>;
+ };
+};
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600_smart_intosc.dtso b/drivers/clk/cirrus/kunit_clk_cs2600_smart_intosc.dtso
new file mode 100644
index 000000000000..893406b49f1c
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600_smart_intosc.dtso
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "kunit_clk_cs2600.dtsi"
+
+&cs2600_kunit_i2c {
+ cs2600: cs2600@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&cs2600_kunit_refclk>, <&cs2600_kunit_clkin>;
+ clock-names = "ref_clk_in", "clk_in";
+ cirrus,internal-oscillator;
+ cirrus,smart-mode;
+ vdd-supply = <&dummy_vreg>;
+ };
+};
diff --git a/drivers/clk/cirrus/kunit_clk_cs2600_smart_refclk.dtso b/drivers/clk/cirrus/kunit_clk_cs2600_smart_refclk.dtso
new file mode 100644
index 000000000000..6f46834cd2de
--- /dev/null
+++ b/drivers/clk/cirrus/kunit_clk_cs2600_smart_refclk.dtso
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include "kunit_clk_cs2600.dtsi"
+
+&cs2600_kunit_i2c {
+ cs2600: cs2600@2c {
+ compatible = "cirrus,cs2600";
+ reg = <0x2c>;
+ #clock-cells = <1>;
+ clocks = <&cs2600_kunit_refclk>, <&cs2600_kunit_clkin>;
+ clock-names = "ref_clk_in", "clk_in";
+ cirrus,smart-mode;
+ vdd-supply = <&dummy_vreg>;
+ };
+};
--
2.47.3
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v6 2/3] clk: cs2600: Add Fractional-N clock driver
2026-06-30 15:55 ` [PATCH v6 2/3] clk: cs2600: Add Fractional-N clock driver Richard Fitzgerald
@ 2026-06-30 16:11 ` sashiko-bot
2026-07-01 9:38 ` Richard Fitzgerald
2026-06-30 16:59 ` Uwe Kleine-König
1 sibling, 1 reply; 10+ messages in thread
From: sashiko-bot @ 2026-06-30 16:11 UTC (permalink / raw)
To: Richard Fitzgerald; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] The `cs2600_unfreeze` guard uses inverted lock/unlock operations, leaving the device permanently frozen.
- [High] Clock rates and dividers are updated without the necessary hardware freeze guard, risking clock glitches.
- [High] Data race on statically defined `parent_names` array in `cs2600_clk_register`.
- [High] The `cs2600_pll_out_recalc_rate` callback returns uninitialized software state instead of interrogating the hardware.
- [High] Refcount underflow in `cs2600_pll_out_unprepare` due to early return in `cs2600_pll_out_prepare`.
- [Medium] The `determine_rate` callbacks incorrectly return `-EINVAL` and emit log spam instead of rounding `req->rate`.
- [Medium] Missing `clk_in` validation in smart mode leads to 0 parents, breaking rate calculation.
--
commit ce7a3c3019cf88623a644142e879bd585ca8f100
Author: Paul Handrigan <paulha@opensource.cirrus.com>
clk: cs2600: Add Fractional-N clock driver
Add driver for the Cirrus Logic CS2600 fractional-N clock synthesizer
and multiplier.
> diff --git a/drivers/clk/cirrus/clk-cs2600.c b/drivers/clk/cirrus/clk-cs2600.c
> new file mode 100644
> index 0000000000000..73937ac868fcd
> --- /dev/null
> +++ b/drivers/clk/cirrus/clk-cs2600.c
[ ... ]
> +static void cs2600_clear_freeze(struct cs2600 *cs2600)
> +{
> + regmap_clear_bits(cs2600->regmap, CS2600_PLL_CFG2, CS2600_FREEZE_EN);
> +}
> +
> +DEFINE_GUARD(cs2600_unfreeze, struct cs2600 *,
> + cs2600_clear_freeze(_T), cs2600_set_freeze(_T))
[Severity: High]
Are the lock and unlock operations inverted here?
The macro DEFINE_GUARD takes the _lock (entry) operation first and _unlock
(exit) operation second. The device defaults to an unfrozen state
(FREEZE_EN=0). By passing cs2600_clear_freeze as the entry operation, it is
initially a no-op, and passing cs2600_set_freeze on exit permanently freezes
the hardware.
Could this cause subsequent writes to critical registers to be ignored or held
indefinitely, breaking the device configuration?
[ ... ]
> +static unsigned long cs2600_pll_out_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
> + struct cs2600 *cs2600 = clk_hw->priv;
> +
> + if ((parent_rate == 0) || (cs2600->pll_target_rate == 0))
> + return 0;
> +
> + if (parent_rate == cs2600->pll_parent_rate)
> + return cs2600->pll_target_rate;
[Severity: High]
Does this function need to interrogate the actual hardware state?
During boot, the Common Clock Framework relies on recalc_rate to determine the
active hardware rate. Because cs2600->pll_target_rate initializes to 0, this
will report 0 Hz even if the bootloader previously configured and enabled the
PLL.
Will this break downstream consumers that depend on inheriting the correct
operational clock rate from the hardware?
[ ... ]
> +static int cs2600_pll_out_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
> + struct regmap *regmap = clk_hw->priv->regmap;
> + struct cs2600 *cs2600 = clk_hw->priv;
> + unsigned int ratio, smart_s_ratio;
> + bool hi_res;
> + int ret;
> +
> + ret = cs2600_calc_ratio(cs2600, rate, parent_rate, &ratio, &hi_res);
[Severity: High]
Should this function acquire the unfreeze guard before modifying the ratio
registers?
This function performs multiple 16-bit regmap_write operations to update a
32-bit ratio. Without suspending the hardware state machine during these
updates, the chip might briefly operate with a corrupted 32-bit ratio
(half old, half new).
Could this missing guard cause clock glitches, frequency spikes, or a
temporary loss of lock?
[ ... ]
> +static int cs2600_pll_out_prepare(struct clk_hw *hw)
> +{
> + struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
> + struct regmap *regmap = clk_hw->priv->regmap;
> + struct cs2600 *cs2600 = clk_hw->priv;
> + int ret;
> +
> + ret = regmap_test_bits(regmap, CS2600_PLL_CFG1, CS2600_PLL_EN1);
> + if (ret < 0)
> + return ret;
> +
> + if (ret)
> + return 0;
[Severity: High]
Will this early return cause a refcount underflow later?
If the PLL is already enabled (for example, by the bootloader), this function
returns 0 immediately and skips calling clk_prepare_enable(cs2600->ref_clk).
However, cs2600_pll_out_unprepare() unconditionally calls
clk_disable_unprepare(cs2600->ref_clk).
When CCF disables an unused clock or a consumer unprepares it, wouldn't the
driver drop a software reference to ref_clk that was never actually acquired,
potentially shutting down the shared reference clock for other hardware?
[ ... ]
> +static int cs2600_bclk_determine_rate(struct clk_hw *hw,
> + struct clk_rate_request *req)
> +{
> + struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
> + struct cs2600 *cs2600 = clk_hw->priv;
> +
> + if (req->rate > CS2600_BCLK_OUT_MAX ||
> + req->rate < CS2600_BCLK_OUT_MIN) {
> + dev_err(cs2600->dev, "Invalid bclk rate %lu", req->rate);
> + return -EINVAL;
> + }
[Severity: Medium]
Should this callback adjust req->rate to the closest supported frequency
instead of returning -EINVAL?
CCF determine_rate callbacks are expected to round the requested rate.
Returning -EINVAL breaks standard rate negotiation like clk_round_rate().
This also applies to cs2600_fsync_determine_rate().
Also, because consumers routinely request unachievable bounds during initial
rate negotiation, printing a dev_err() here may cause unnecessary kernel
log spam.
[ ... ]
> +static int cs2600_clk_register(struct cs2600 *cs2600)
> +{
> + static const char *parent_names[2];
[Severity: High]
Is it safe to share this static array across all driver instances without
synchronization?
If multiple CS2600 devices are probed simultaneously via asynchronous probing
or on independent buses, their probe threads could concurrently overwrite
this static array before calling devm_clk_hw_register().
Would this corrupt the clock topologies, causing one device to register with
the parent names intended for another?
> + const struct clk_hw *out_clocks_parent;
> + struct clk_init_data init[ARRAY_SIZE(cs2600->hw)] = { };
> + int ret, i, n, input_num = 0;
[ ... ]
> + /* ref_clk_in is only a parent option in manual mode */
> + if (!cs2600_is_smart_mode(cs2600))
> + parent_names[input_num++] = __clk_get_name(cs2600->ref_clk);
> +
> + if (cs2600->clk_in)
> + parent_names[input_num++] = __clk_get_name(cs2600->clk_in);
> +
> + init[CS2600_PLL_OUT].parent_names = parent_names;
> + init[CS2600_PLL_OUT].num_parents = input_num;
[Severity: Medium]
What happens if clk_in is NULL when the device operates in smart mode?
The driver fetches clk_in as optional using devm_clk_get_optional(). If
cs2600_is_smart_mode() is true and clk_in is missing from the device tree,
input_num will be 0.
Registering the PLL with num_parents = 0 leaves the PLL without a valid
parent clock in the CCF. Will this cause downstream frequency calculations
to fail or evaluate to 0, despite the hardware relying on an external clock?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260630155549.824059-1-rf@opensource.cirrus.com?part=2
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v6 3/3] clk: cs2600: Add KUnit test for CS2600 driver
2026-06-30 15:55 ` [PATCH v6 3/3] clk: cs2600: Add KUnit test for CS2600 driver Richard Fitzgerald
@ 2026-06-30 16:14 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-06-30 16:14 UTC (permalink / raw)
To: Richard Fitzgerald; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [Medium] 32-bit integer overflow in the calculation of `fdelta` tightens the acceptable error bounds, potentially causing false test failures.
- [Medium] `clk_prepare_enable_kunit()` is repeatedly invoked inside loops, bypassing hardware enable paths after the first iteration and accumulating redundant cleanup actions.
- [Medium] The test verifies the ratio slot using a stale copy of the PLL configuration registers read prior to `clk_set_rate()`.
- [Low] Evaluating a device tree node pointer value after dropping its reference with `of_node_put()`.
- [Low] The Kconfig entry for `COMMON_CLK_CS2600_KUNIT_TEST` fails to `select DTC`, leading to potential build failures.
- [Low] The mock I2C transfer function `cs2600_kunit_i2c_xfer` unconditionally accesses message buffers without validating message count or length.
--
commit da440c9cd81c522da5fe16912468ea26cbaf9db1
Author: Richard Fitzgerald <rf@opensource.cirrus.com>
Date: Tue Jun 30 15:55:49 2026 +0000
clk: cs2600: Add KUnit test for CS2600 driver
This patch adds a KUnit test for the CS2600 clock driver using a mock I2C host
driver and regmap to partially emulate the hardware. The test relies on hardcoded
tables to validate the frequency ratio and PLL configuration settings, and uses
devicetree overlays to validate different hardware properties.
> diff --git a/drivers/clk/cirrus/Kconfig b/drivers/clk/cirrus/Kconfig
> index 5674d6554f966..a6807cab4b945 100644
> --- a/drivers/clk/cirrus/Kconfig
> +++ b/drivers/clk/cirrus/Kconfig
> @@ -8,3 +8,16 @@ config COMMON_CLK_CS2600
> help
> If you say yes here you get support for the CS2600 clock synthesizer
> and multiplier.
> +
> +config COMMON_CLK_CS2600_KUNIT_TEST
> + tristate "KUnit test for CS2600 clock driver" if !KUNIT_ALL_TESTS
> + depends on COMMON_CLK_CS2600
> + depends on KUNIT
> + depends on REGULATOR
> + depends on REGULATOR_FIXED_VOLTAGE
> + default KUNIT_ALL_TESTS
[Severity: Low]
Does this Kconfig entry need to select DTC? The test's Makefile builds Device
Tree overlays (.dtbo files), which could cause build failures if CONFIG_DTC
is disabled.
> + help
> + Build the KUnit tests for the Cirrus Logic CS2600
> + clock driver.
> +
> + If unsure say N.
[ ... ]
> diff --git a/drivers/clk/cirrus/clk-cs2600-kunit.c b/drivers/clk/cirrus/clk-cs2600-kunit.c
> new file mode 100644
> index 0000000000000..9631801ac553b
> --- /dev/null
> +++ b/drivers/clk/cirrus/clk-cs2600-kunit.c
[ ... ]
> +static void cs2600_kunit_synth_ratio(struct kunit *test)
> +{
> + const struct cs2600_kunit_params *params = test->param_value;
> + const struct cs2600_kunit_ratio_config *ratios = params->s_ratios;
> + struct cs2600_kunit_priv *priv = test->priv;
> + unsigned int ratio_sel, pll_cfg2, ratio;
> + int i;
> +
> + KUNIT_ASSERT_NOT_NULL(test, ratios);
> +
> + for (i = 0; ratios[i].clk_out != 0; i++) {
> + /* 6 MHz is out-of-range for some input clocks */
> + if (ratios[i].r == 0) {
> + KUNIT_ASSERT_EQ(test, ratios[i].clk_out, 6000000);
> + continue;
> + }
> +
> + KUNIT_EXPECT_EQ_MSG(test, 0,
> + clk_set_rate(priv->clkout, ratios[i].clk_out),
> + "clk_out:%u s:%#x\n",
> + ratios[i].clk_out, ratios[i].r);
> + KUNIT_EXPECT_EQ_MSG(test, 0,
> + clk_prepare_enable_kunit(test, priv->clkout),
[Severity: Medium]
Is it intentional to call clk_prepare_enable_kunit() in every loop iteration
without a matching disable? This will repeatedly increment the clock's reference
count and accumulate deferred cleanup actions, which might bypass the actual
hardware enable paths on subsequent loop iterations. This also appears to happen
in cs2600_kunit_mult_ratio() and cs2600_kunit_smart_ratio().
> + "clk_out:%u s:%#x\n",
> + ratios[i].clk_out, ratios[i].r);
> +
> + KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG1, &ratio_sel));
[ ... ]
> +static void cs2600_kunit_change_parent_same_clkout(struct kunit *test)
> +{
> + const struct cs2600_kunit_params *params = test->param_value;
> + struct cs2600_kunit_priv *priv = test->priv;
> + struct clk *refclk = clk_hw_get_clk_kunit(test, priv->refclk_hw, NULL);
> + struct clk *clkin = clk_hw_get_clk_kunit(test, priv->clkin_hw, NULL);
> + unsigned int ratio_sel, pll_cfg1, pll_cfg2, pll_cfg3, ratio;
> + int i;
[ ... ]
> + /* clk_set_rate() should recalculate based on new parent frequency */
> + KUNIT_EXPECT_EQ_MSG(test, 0,
> + clk_set_rate(priv->clkout, params->m_ratios[i].clk_out),
> + "clk_out:%u\n", params->m_ratios[i].clk_out);
> +
> + ratio_sel = FIELD_GET(CS2600_M_RATIO_SEL_MASK, pll_cfg2);
[Severity: Medium]
Does pll_cfg2 contain the updated register state here? The test appears to
validate the ratio slot using a stale copy of the PLL configuration registers
(pll_cfg2, and later pll_cfg1) read prior to the clk_set_rate() call, which
might fail to verify the newly updated hardware state.
> + ratio = cs2600_kunit_get_ratio_val(test, ratio_sel);
> + KUNIT_EXPECT_EQ_MSG(test, params->m_ratios[i].r, ratio,
> + "clk_out:%u\n", params->m_ratios[i].clk_out);
> +
> + /*
> + * Change back to default parent and it should be synth mode again
> + */
> + KUNIT_EXPECT_EQ(test, 0, clk_set_parent(priv->pllout, refclk));
> +
> + KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG1, &pll_cfg1));
> + KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG2, &pll_cfg2));
> + KUNIT_ASSERT_EQ(test, 0, regmap_read(priv->regmap, CS2600_PLL_CFG3, &pll_cfg3));
> +
> + KUNIT_EXPECT_EQ(test, 0, FIELD_GET(CS2600_PLL_MODE_SEL, pll_cfg2));
> + KUNIT_EXPECT_EQ(test, FIELD_GET(CS2600_S_RATIO_SEL_MASK, pll_cfg1),
> + FIELD_GET(CS2600_M_RATIO_SEL_MASK, pll_cfg2));
> +
> + /* Ratio should have changed if reference frequency has changed */
> + if (clk_get_rate(refclk) != clk_get_rate(clkin)) {
> + ratio_sel = FIELD_GET(CS2600_S_RATIO_SEL_MASK, pll_cfg1);
> + ratio = cs2600_kunit_get_ratio_val(test, ratio_sel);
> + KUNIT_EXPECT_NE_MSG(test, params->m_ratios[i].r, ratio,
> + "clk_out:%u\n", params->s_ratios[i].clk_out);
> + }
> +
> + /* clk_set_rate() should recalculate based on new parent frequency */
> + KUNIT_EXPECT_EQ_MSG(test, 0,
> + clk_set_rate(priv->clkout, params->s_ratios[i].clk_out),
> + "clk_out:%u\n", params->s_ratios[i].clk_out);
> +
> + ratio_sel = FIELD_GET(CS2600_S_RATIO_SEL_MASK, pll_cfg1);
[Severity: Medium]
Similar to the issue above, is pll_cfg1 stale here because it was read before
clk_set_rate() updated the hardware?
> + ratio = cs2600_kunit_get_ratio_val(test, ratio_sel);
> + KUNIT_EXPECT_EQ_MSG(test, params->s_ratios[i].r, ratio,
> + "clk_out:%u\n", params->m_ratios[i].clk_out);
> + }
> +}
[ ... ]
> +static void cs2600_kunit_bclk_divider(struct kunit *test)
> +{
> + struct cs2600_kunit_priv *priv = test->priv;
> + unsigned int out_cfg1, bclk_div, fdelta;
> + int i;
> +
> + for (i = 0; cs2600_kunit_bclk_divs[i].pll_out != 0; i++) {
[ ... ]
> + /*
> + * Actual reported frequency can be slightly out because of
> + * PLL resolution, but should be within worst-case PPM.
> + */
> + fdelta = (cs2600_kunit_bclk_divs[i].bf_out * CS2600_20_12_PPM) >> 20;
[Severity: Medium]
Can this multiplication overflow a 32-bit integer? The maximum bf_out value is
24,576,000, and CS2600_20_12_PPM is 224, making their product 5,505,024,000,
which exceeds the 32-bit UINT_MAX. This could evaluate to an artificially tight
tolerance range, potentially causing false test failures.
> + KUNIT_EXPECT_GE_MSG(test, clk_get_rate(priv->bclk),
> + cs2600_kunit_bclk_divs[i].bf_out - fdelta,
> + "clk_out:%u bclk:%u\n",
> + cs2600_kunit_bclk_divs[i].pll_out,
> + cs2600_kunit_bclk_divs[i].bf_out);
[ ... ]
> +static int cs2600_kunit_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
> +{
> + struct cs2600_kunit_priv *priv = i2c_get_adapdata(adap);
> + unsigned int addr, val;
> + __be16 *bebuf;
> + int ret;
> +
> + /* First word of first message is always the register address */
> + bebuf = (__force __be16 *)msgs[0].buf;
> + addr = be16_to_cpu(bebuf[0]);
[Severity: Low]
Could this unconditionally dereference memory? The code appears to access
msgs[0].buf without first verifying that num >= 1 and msgs[0].len >=
sizeof(__be16).
> +
> + /* A second word in the first message is a data write */
> + if (msgs[0].len >= 2 * sizeof(__be16)) {
> + val = be16_to_cpu(bebuf[1]);
> + ret = cs2600_kunit_i2c_write(priv, addr, val);
> + if (ret < 0)
> + return ret;
> +
> + return 1;
> + }
[ ... ]
> +static int cs2600_kunit_case_common_init(struct kunit *test)
> +{
> + const struct cs2600_kunit_params *params = test->param_value;
> + struct cs2600_kunit_priv *priv;
> + struct device_node *np;
[ ... ]
> + /* Wait for probes if the test case init already loaded the overlay */
> + np = of_find_node_by_name(NULL, "cs2600-test-consumer");
> + of_node_put(np);
> + if (!IS_ERR_OR_NULL(np))
[Severity: Low]
Is it safe to evaluate np with IS_ERR_OR_NULL() after its reference has been
dropped by of_node_put()? Although IS_ERR_OR_NULL() only performs address
evaluation, relying on a conceptually dangling pointer value might lead to
unintended behavior.
> + cs2600_kunit_wait_for_probes(test);
> +
> + return 0;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260630155549.824059-1-rf@opensource.cirrus.com?part=3
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v6 2/3] clk: cs2600: Add Fractional-N clock driver
2026-06-30 15:55 ` [PATCH v6 2/3] clk: cs2600: Add Fractional-N clock driver Richard Fitzgerald
2026-06-30 16:11 ` sashiko-bot
@ 2026-06-30 16:59 ` Uwe Kleine-König
1 sibling, 0 replies; 10+ messages in thread
From: Uwe Kleine-König @ 2026-06-30 16:59 UTC (permalink / raw)
To: Richard Fitzgerald
Cc: mturquette, sboyd, bmasney, robh, krzk+dt, conor+dt, linux-clk,
devicetree, linux-kernel, patches
[-- Attachment #1: Type: text/plain, Size: 303 bytes --]
On Tue, Jun 30, 2026 at 04:55:48PM +0100, Richard Fitzgerald wrote:
> +static const struct i2c_device_id cs2600_id[] = {
> + { "cs2600", },
> + {}
> +};
Please make this:
static const struct i2c_device_id cs2600_id[] = {
{ .name = "cs2600" },
{ }
};
to match the common style.
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v6 1/3] dt-bindings: clock: cs2600: Add support for the CS2600
2026-06-30 15:55 ` [PATCH v6 1/3] dt-bindings: clock: cs2600: Add support for the CS2600 Richard Fitzgerald
@ 2026-07-01 6:48 ` Krzysztof Kozlowski
2026-07-01 8:59 ` Richard Fitzgerald
0 siblings, 1 reply; 10+ messages in thread
From: Krzysztof Kozlowski @ 2026-07-01 6:48 UTC (permalink / raw)
To: Richard Fitzgerald
Cc: mturquette, sboyd, bmasney, robh, krzk+dt, conor+dt, linux-clk,
devicetree, linux-kernel, patches
On Tue, Jun 30, 2026 at 04:55:47PM +0100, Richard Fitzgerald wrote:
> From: Paul Handrigan <paulha@opensource.cirrus.com>
>
> Add device tree schema for the Cirrus Logic CS2600 clock generator.
>
> The majority of the schema is typical clock, power and I2C
> properties.
>
> Passes dt_binding_check:
> make dt_binding_check DT_SCHEMA_FILES=clock/cirrus,cs2600.yaml
> SCHEMA Documentation/devicetree/bindings/processed-schema.json
> CHKDT ./Documentation/devicetree/bindings
> LINT ./Documentation/devicetree/bindings
> STYLE ./Documentation/devicetree/bindings
> DTEX Documentation/devicetree/bindings/clock/cirrus,cs2600.example.dts
> DTC [C] Documentation/devicetree/bindings/clock/cirrus,cs2600.example.dtb
Why is this in the commit msg? What is its purpose? Do you see any of
this in any commits?
>
> Signed-off-by: Paul Handrigan <paulha@opensource.cirrus.com>
> Co-developed-by: Richard Fitzgerald <rf@opensource.cirrus.com>
> Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
> ---
>
> Changes in V6:
> - Fixed clock naming in descriptions to match datasheet.
> - Fixed clock-names property values to match datasheet names.
>
> - Added cirrus,internal-oscillator boolean to flag that the internal
> oscillator is the clock source. Previously the driver inferred this
> if clock-names did not contain "ref_clk_in", but this made it difficult
> to enforce dts correctness because there was no way to tell whether
> ref_clk_in was intentionally or accidentally missing.
>
> - Changed the cirrus,clock-mode enum property into two booleans, since
> effectively it was two separate features masquerading as an enum:
> - cirrus,smart-mode present to enable smart mode.
> - cirrus,smart-mode-clkin-only to enable a feature where the output will
> be suppressed until both input clocks are present.
>
> - Changes to cirrus,aux-output-source property:
> - Renamed to cirrus,aux1-output-source because it's for the AUX1 pin.
> - Added more options.
> - Renamed the "no_clkin" option to "clkin_missing".
> - Reformatted the description as a list instead of one long sentence.
>
> - Changed clock-names from an enum to an ordered list of const.
> This implicitly ensures ref_clk_in is always required.
>
> - Added properties to invert bclk and fsync outputs.
> - Added property cirrus,fsync-duty-cycles.
> - Added #clock-cells and vdd-supply to the list of required properies.
> - Rewritten description description section.
> - Reordered the property list to put common properties before custom
> cirrus properties.
> - Added more examples.
> - Added header file to define the clock indexes for DT consumers of the
> CS2600 clocks.
>
> Note:
> V5 was Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
> but I haven't carried this forward because the changes in V6 are large.
Dropping my tag is right thing to do, but making significant changes to
hardware at v6 is very odd. Bindings represent here the hardware, so how
is that v5 had one view of hardware and the next revision rewrites it
completely.
Probably answer - v5 was heavily incomplete - but we do ask to make it
complete in the first place (see writing bindings), so all my previous
review was waste of time.
This will wait for review.
> +examples:
> + - |
> + /* Smart mode */
> + i2c {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + clock-controller@2c {
> + compatible = "cirrus,cs2600";
> + reg = <0x2c>;
> + #clock-cells = <1>;
> + clocks = <&xtl_clk>, <&sync_clock>;
> + clock-names = "ref_clk_in", "clk_in";
> + vdd-supply = <&vreg>;
> + cirrus,smart-mode;
> + };
> + };
> +
Two examples max.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v6 1/3] dt-bindings: clock: cs2600: Add support for the CS2600
2026-07-01 6:48 ` Krzysztof Kozlowski
@ 2026-07-01 8:59 ` Richard Fitzgerald
0 siblings, 0 replies; 10+ messages in thread
From: Richard Fitzgerald @ 2026-07-01 8:59 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: mturquette, sboyd, bmasney, robh, krzk+dt, conor+dt, linux-clk,
devicetree, linux-kernel, patches
On 01/07/2026 7:48 am, Krzysztof Kozlowski wrote:
> On Tue, Jun 30, 2026 at 04:55:47PM +0100, Richard Fitzgerald wrote:
>> From: Paul Handrigan <paulha@opensource.cirrus.com>
>>
>> Add device tree schema for the Cirrus Logic CS2600 clock generator.
>>
>> The majority of the schema is typical clock, power and I2C
>> properties.
>>
>> Passes dt_binding_check:
>> make dt_binding_check DT_SCHEMA_FILES=clock/cirrus,cs2600.yaml
>> SCHEMA Documentation/devicetree/bindings/processed-schema.json
>> CHKDT ./Documentation/devicetree/bindings
>> LINT ./Documentation/devicetree/bindings
>> STYLE ./Documentation/devicetree/bindings
>> DTEX Documentation/devicetree/bindings/clock/cirrus,cs2600.example.dts
>> DTC [C] Documentation/devicetree/bindings/clock/cirrus,cs2600.example.dtb
>
> Why is this in the commit msg? What is its purpose? Do you see any of
> this in any commits?
>
Purpose is to inform people that I ran the check and it passes.
If you don't want that information I'm fine with that. I'll leave it out
in V7.
>
>>
>> Signed-off-by: Paul Handrigan <paulha@opensource.cirrus.com>
>> Co-developed-by: Richard Fitzgerald <rf@opensource.cirrus.com>
>> Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
>> ---
>>
>> Changes in V6:
>> - Fixed clock naming in descriptions to match datasheet.
>> - Fixed clock-names property values to match datasheet names.
>>
>> - Added cirrus,internal-oscillator boolean to flag that the internal
>> oscillator is the clock source. Previously the driver inferred this
>> if clock-names did not contain "ref_clk_in", but this made it difficult
>> to enforce dts correctness because there was no way to tell whether
>> ref_clk_in was intentionally or accidentally missing.
>>
>> - Changed the cirrus,clock-mode enum property into two booleans, since
>> effectively it was two separate features masquerading as an enum:
>> - cirrus,smart-mode present to enable smart mode.
>> - cirrus,smart-mode-clkin-only to enable a feature where the output will
>> be suppressed until both input clocks are present.
>>
>> - Changes to cirrus,aux-output-source property:
>> - Renamed to cirrus,aux1-output-source because it's for the AUX1 pin.
>> - Added more options.
>> - Renamed the "no_clkin" option to "clkin_missing".
>> - Reformatted the description as a list instead of one long sentence.
>>
>> - Changed clock-names from an enum to an ordered list of const.
>> This implicitly ensures ref_clk_in is always required.
>>
>> - Added properties to invert bclk and fsync outputs.
>> - Added property cirrus,fsync-duty-cycles.
>> - Added #clock-cells and vdd-supply to the list of required properies.
>> - Rewritten description description section.
>> - Reordered the property list to put common properties before custom
>> cirrus properties.
>> - Added more examples.
>> - Added header file to define the clock indexes for DT consumers of the
>> CS2600 clocks.
>>
>> Note:
>> V5 was Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
>> but I haven't carried this forward because the changes in V6 are large.
>
> Dropping my tag is right thing to do, but making significant changes to
> hardware at v6 is very odd. Bindings represent here the hardware, so how
> is that v5 had one view of hardware and the next revision rewrites it
> completely.
>
> Probably answer - v5 was heavily incomplete - but we do ask to make it
> complete in the first place (see writing bindings), so all my previous
> review was waste of time.
Unfortunately I can't do anything about previous versions. I apologize
on behalf of Cirrus that they could have been better. I've made what I
think are improvements in V6.
V5 was sent in Dec 2024. That's over a year to (a) find issues with V5
and (b) incorporate learnings from non-Linux products about how the
CS2600 will be used by manufacturers.
>
> This will wait for review.
>
>> +examples:
>> + - |
>> + /* Smart mode */
>> + i2c {
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> +
>> + clock-controller@2c {
>> + compatible = "cirrus,cs2600";
>> + reg = <0x2c>;
>> + #clock-cells = <1>;
>> + clocks = <&xtl_clk>, <&sync_clock>;
>> + clock-names = "ref_clk_in", "clk_in";
>> + vdd-supply = <&vreg>;
>> + cirrus,smart-mode;
>> + };
>> + };
>> +
>
> Two examples max.
>
That's an unfortunate rule. But if that's the rule I'll trim it
to 2.
> Best regards,
> Krzysztof
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v6 2/3] clk: cs2600: Add Fractional-N clock driver
2026-06-30 16:11 ` sashiko-bot
@ 2026-07-01 9:38 ` Richard Fitzgerald
0 siblings, 0 replies; 10+ messages in thread
From: Richard Fitzgerald @ 2026-07-01 9:38 UTC (permalink / raw)
To: sashiko-reviews; +Cc: robh, conor+dt, devicetree
On 30/06/2026 5:11 pm, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
> - [High] The `cs2600_unfreeze` guard uses inverted lock/unlock operations, leaving the device permanently frozen.
> - [High] Clock rates and dividers are updated without the necessary hardware freeze guard, risking clock glitches.
> - [High] Data race on statically defined `parent_names` array in `cs2600_clk_register`.
> - [High] The `cs2600_pll_out_recalc_rate` callback returns uninitialized software state instead of interrogating the hardware.
> - [High] Refcount underflow in `cs2600_pll_out_unprepare` due to early return in `cs2600_pll_out_prepare`.
> - [Medium] The `determine_rate` callbacks incorrectly return `-EINVAL` and emit log spam instead of rounding `req->rate`.
> - [Medium] Missing `clk_in` validation in smart mode leads to 0 parents, breaking rate calculation.
> --
>
Gemini has found some real issues and some bogus stuff, which I've
commented on below.
I'll hold off on doing a V7 to fix the real issues, to give people
a chance to add other reviews.
> commit ce7a3c3019cf88623a644142e879bd585ca8f100
> Author: Paul Handrigan <paulha@opensource.cirrus.com>
>
> clk: cs2600: Add Fractional-N clock driver
>
> Add driver for the Cirrus Logic CS2600 fractional-N clock synthesizer
> and multiplier.
>
>> diff --git a/drivers/clk/cirrus/clk-cs2600.c b/drivers/clk/cirrus/clk-cs2600.c
>> new file mode 100644
>> index 0000000000000..73937ac868fcd
>> --- /dev/null
>> +++ b/drivers/clk/cirrus/clk-cs2600.c
>
> [ ... ]
>
>> +static void cs2600_clear_freeze(struct cs2600 *cs2600)
>> +{
>> + regmap_clear_bits(cs2600->regmap, CS2600_PLL_CFG2, CS2600_FREEZE_EN);
>> +}
>> +
>> +DEFINE_GUARD(cs2600_unfreeze, struct cs2600 *,
>> + cs2600_clear_freeze(_T), cs2600_set_freeze(_T))
>
> [Severity: High]
> Are the lock and unlock operations inverted here?
>
No, they are not. FREEZE_EN must be cleared to write protected bits and
set after they have been written.
> [ ... ]
>
>> +static unsigned long cs2600_pll_out_recalc_rate(struct clk_hw *hw,
>> + unsigned long parent_rate)
>> +{
>> + struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
>> + struct cs2600 *cs2600 = clk_hw->priv;
>> +
>> + if ((parent_rate == 0) || (cs2600->pll_target_rate == 0))
>> + return 0;
>> +
>> + if (parent_rate == cs2600->pll_parent_rate)
>> + return cs2600->pll_target_rate;
>
> [Severity: High]
> Does this function need to interrogate the actual hardware state?
If the parent rate hasn't changed, the output rate hasn't changed.
This is to prevent multiple fractional->integer conversions causing
the set frequency to drift away from the best frequency.
>
> During boot, the Common Clock Framework relies on recalc_rate to determine the
> active hardware rate. Because cs2600->pll_target_rate initializes to 0, this
> will report 0 Hz even if the bootloader previously configured and enabled the
> PLL.
>
> Will this break downstream consumers that depend on inheriting the correct
> operational clock rate from the hardware?
The driver resets the CS2600 during probe.
>
> [ ... ]
>
>> +static int cs2600_pll_out_set_rate(struct clk_hw *hw, unsigned long rate,
>> + unsigned long parent_rate)
>> +{
>> + struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
>> + struct regmap *regmap = clk_hw->priv->regmap;
>> + struct cs2600 *cs2600 = clk_hw->priv;
>> + unsigned int ratio, smart_s_ratio;
>> + bool hi_res;
>> + int ret;
>> +
>> + ret = cs2600_calc_ratio(cs2600, rate, parent_rate, &ratio, &hi_res);
>
> [Severity: High]
> Should this function acquire the unfreeze guard before modifying the ratio
> registers?
No, FREEZE_EN doesn't affect the ratio registers.
>
> This function performs multiple 16-bit regmap_write operations to update a
> 32-bit ratio. Without suspending the hardware state machine during these
> updates, the chip might briefly operate with a corrupted 32-bit ratio
> (half old, half new).
>
> Could this missing guard cause clock glitches, frequency spikes, or a
> temporary loss of lock?
It's a PLL. The output frequency will drift to the new frequency, and
this can be preferable to a hard switch.
However, I will double-check with hardware people whether we should
enforce that the clock is stopped around a ratio change instead of
>
> [ ... ]
>
>> +static int cs2600_pll_out_prepare(struct clk_hw *hw)
>> +{
>> + struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
>> + struct regmap *regmap = clk_hw->priv->regmap;
>> + struct cs2600 *cs2600 = clk_hw->priv;
>> + int ret;
>> +
>> + ret = regmap_test_bits(regmap, CS2600_PLL_CFG1, CS2600_PLL_EN1);
>> + if (ret < 0)
>> + return ret;
>> +
>> + if (ret)
>> + return 0;
>
> [Severity: High]
> Will this early return cause a refcount underflow later?
This quick exit is something in V5 I hadn't noticed, and it's a bit
strange. This test should always return false.
>
> [ ... ]
>
>> +static int cs2600_bclk_determine_rate(struct clk_hw *hw,
>> + struct clk_rate_request *req)
>> +{
>> + struct cs2600_clk_hw *clk_hw = hw_to_cs2600_clk(hw);
>> + struct cs2600 *cs2600 = clk_hw->priv;
>> +
>> + if (req->rate > CS2600_BCLK_OUT_MAX ||
>> + req->rate < CS2600_BCLK_OUT_MIN) {
>> + dev_err(cs2600->dev, "Invalid bclk rate %lu", req->rate);
>> + return -EINVAL;
>> + }
>
> [Severity: Medium]
> Should this callback adjust req->rate to the closest supported frequency
> instead of returning -EINVAL?
Nope, there's no way that this driver can determine what an appropriate
parent rate would be. The parent rate is likely clocking some hardware
via CLK_OUT and we can't arbitrarily change that rate.
> [ ... ]
>
>> +static int cs2600_clk_register(struct cs2600 *cs2600)
>> +{
>> + static const char *parent_names[2];
>
> [Severity: High]
> Is it safe to share this static array across all driver instances without
> synchronization?
Valid. This static should not be here.
This is a bug inherited from V5 that had not been picked up by any
automated or manual code reviews until Gemini took a look.
> [ ... ]
>
>> + /* ref_clk_in is only a parent option in manual mode */
>> + if (!cs2600_is_smart_mode(cs2600))
>> + parent_names[input_num++] = __clk_get_name(cs2600->ref_clk);
>> +
>> + if (cs2600->clk_in)
>> + parent_names[input_num++] = __clk_get_name(cs2600->clk_in);
>> +
>> + init[CS2600_PLL_OUT].parent_names = parent_names;
>> + init[CS2600_PLL_OUT].num_parents = input_num;
>
> [Severity: Medium]
> What happens if clk_in is NULL when the device operates in smart mode?
Yes, this is a bug.
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-07-01 9:50 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-30 15:55 [PATCH v6 0/3] Cirrus Logic CS2600 clock device Richard Fitzgerald
2026-06-30 15:55 ` [PATCH v6 1/3] dt-bindings: clock: cs2600: Add support for the CS2600 Richard Fitzgerald
2026-07-01 6:48 ` Krzysztof Kozlowski
2026-07-01 8:59 ` Richard Fitzgerald
2026-06-30 15:55 ` [PATCH v6 2/3] clk: cs2600: Add Fractional-N clock driver Richard Fitzgerald
2026-06-30 16:11 ` sashiko-bot
2026-07-01 9:38 ` Richard Fitzgerald
2026-06-30 16:59 ` Uwe Kleine-König
2026-06-30 15:55 ` [PATCH v6 3/3] clk: cs2600: Add KUnit test for CS2600 driver Richard Fitzgerald
2026-06-30 16:14 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox