* [PATCH v2 00/20] drm/atomic: Rework initial state allocation
From: Maxime Ripard @ 2026-03-20 16:27 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Dmitry Baryshkov, Jyri Sarha,
Tomi Valkeinen, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Simon Ser,
Harry Wentland, Melissa Wen, Sebastian Wick, Alex Hung,
Jani Nikula, Rodrigo Vivi, Joonas Lahtinen, Tvrtko Ursulin,
Chen-Yu Tsai, Samuel Holland, Dave Stevenson, Maíra Canal,
Raspberry Pi Kernel Maintenance
Cc: dri-devel, linux-doc, linux-kernel, Daniel Stone, intel-gfx,
intel-xe, linux-arm-kernel, linux-sunxi, Maxime Ripard,
Laurent Pinchart, Laurent Pinchart
Hi,
This series started from my work on the hardware state readout[1], and
more specifically a discussion with Thomas[2].
This series expands the work that has been merged recently to make
drm_private_obj and drm_private_state allocation a bit more consistent
and ended up creating a new atomic_create_state callback to allocate a
new state with no side effect.
The first patches are a documentation of the existing behaviour and some
random cleanups.
Then, we add the new atomic_create_state callback to every other DRM
object. Next, we leverage those new callbacks to create a new helper,
drm_mode_config_create_state() to create the initial state for all the
objects of a driver.
Finally, we hook that new helper in drm_dev_register and start
converting a few drivers.
This was tested on a TI SK-AM62, with the tidss driver.
Let me know what you think,
Maxime
1: https://lore.kernel.org/dri-devel/20250902-drm-state-readout-v1-0-14ad5315da3f@kernel.org/
2: https://lore.kernel.org/dri-devel/5920ffe5-b6b1-484b-b320-332b9eb9db82@suse.de/
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
Changes in v2:
- Change the _state_reset function names to _state_init
- Change the colorop too
- Various doc improvements
- Link to v1: https://lore.kernel.org/r/20260310-drm-mode-config-init-v1-0-de7397c8e1cf@kernel.org
---
Maxime Ripard (20):
drm/colorop: Fix typos in the doc
drm/atomic: Drop drm_private_state.obj assignment from create_state
drm/mode-config: Mention drm_mode_config_reset() culprits
drm/colorop: Rename __drm_colorop_state_reset()
drm/colorop: Create drm_atomic_helper_colorop_create_state()
drm/atomic-state-helper: Fix __drm_atomic_helper_plane_reset() doc typo
drm/atomic-state-helper: Rename __drm_atomic_helper_plane_state_reset()
drm/plane: Add new atomic_create_state callback
drm/atomic-state-helper: Rename __drm_atomic_helper_crtc_state_reset()
drm/crtc: Add new atomic_create_state callback
drm/atomic-state-helper: Rename __drm_atomic_helper_connector_state_reset()
drm/hdmi: Rename __drm_atomic_helper_connector_hdmi_reset()
drm/connector: Add new atomic_create_state callback
drm/mode-config: Create drm_mode_config_create_state()
drm/drv: Call drm_mode_config_create_state() by default
drm/atomic: Drop private obj state allocation
drm/drv: Drop drm_mode_config_reset() from our skeleton
drm/tidss: Drop call to drm_mode_config_reset at probe time
drm/tidss: Convert to atomic_create_state
drm/bridge_connector: Convert to atomic_create_state
drivers/gpu/drm/display/drm_bridge_connector.c | 17 ++-
drivers/gpu/drm/display/drm_hdmi_state_helper.c | 14 +-
drivers/gpu/drm/drm_atomic.c | 19 +--
drivers/gpu/drm/drm_atomic_state_helper.c | 170 ++++++++++++++++++---
drivers/gpu/drm/drm_colorop.c | 41 +++--
drivers/gpu/drm/drm_drv.c | 6 +-
drivers/gpu/drm/drm_mode_config.c | 154 ++++++++++++++++++-
drivers/gpu/drm/i915/display/intel_crtc.c | 2 +-
drivers/gpu/drm/i915/display/intel_plane.c | 2 +-
drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 2 +-
drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c | 2 +-
drivers/gpu/drm/tidss/tidss_crtc.c | 17 +--
drivers/gpu/drm/tidss/tidss_drv.c | 2 -
drivers/gpu/drm/tidss/tidss_kms.c | 2 -
drivers/gpu/drm/tidss/tidss_plane.c | 2 +-
drivers/gpu/drm/vc4/vc4_hdmi.c | 2 +-
include/drm/display/drm_hdmi_state_helper.h | 4 +-
include/drm/drm_atomic_state_helper.h | 18 ++-
include/drm/drm_colorop.h | 2 +
include/drm/drm_connector.h | 13 ++
include/drm/drm_crtc.h | 13 ++
include/drm/drm_mode_config.h | 1 +
include/drm/drm_plane.h | 13 ++
23 files changed, 441 insertions(+), 77 deletions(-)
---
base-commit: cd5c40f745f10f082f75520ebc9deae3ea37b185
change-id: 20260310-drm-mode-config-init-1e1f52b745d0
prerequisite-change-id: 20251008-drm-private-obj-reset-ae1e2741027a:v5
prerequisite-patch-id: 9684f0ca4b16455c1340409561e8fb32f98b327a
prerequisite-patch-id: e177eb92b269436a94d7ef603d44436799be7469
prerequisite-patch-id: fef6d20ab33358c1db6cd9d21aa8ec0990cae758
prerequisite-patch-id: 2dcc96d43f34d8f6237829ed29d0087c092954d1
Best regards,
--
Maxime Ripard <mripard@kernel.org>
^ permalink raw reply
* [PATCH v2 01/20] drm/colorop: Fix typos in the doc
From: Maxime Ripard @ 2026-03-20 16:27 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Dmitry Baryshkov, Jyri Sarha,
Tomi Valkeinen, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Simon Ser,
Harry Wentland, Melissa Wen, Sebastian Wick, Alex Hung,
Jani Nikula, Rodrigo Vivi, Joonas Lahtinen, Tvrtko Ursulin,
Chen-Yu Tsai, Samuel Holland, Dave Stevenson, Maíra Canal,
Raspberry Pi Kernel Maintenance
Cc: dri-devel, linux-doc, linux-kernel, Daniel Stone, intel-gfx,
intel-xe, linux-arm-kernel, linux-sunxi, Maxime Ripard
In-Reply-To: <20260320-drm-mode-config-init-v2-0-c63f1134e76c@kernel.org>
In the documentation of drm_colorop introduced by commit cfc27680ee20
("drm/colorop: Introduce new drm_colorop mode object"), the
documentation of __drm_colorop_state_reset() and __drm_colorop_reset()
were mentioning CRTC when they really meant colorop, probably due to
copy and paste.
Fixes: cfc27680ee20 ("drm/colorop: Introduce new drm_colorop mode object")
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_colorop.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/drm_colorop.c b/drivers/gpu/drm/drm_colorop.c
index 566816e3c6f0c7d172534966fcfe56982e6505f3..373cd0ddb8fd4478874509ed12c95451c1f66203 100644
--- a/drivers/gpu/drm/drm_colorop.c
+++ b/drivers/gpu/drm/drm_colorop.c
@@ -503,11 +503,11 @@ void drm_colorop_atomic_destroy_state(struct drm_colorop *colorop,
* __drm_colorop_state_reset - resets colorop state to default values
* @colorop_state: atomic colorop state, must not be NULL
* @colorop: colorop object, must not be NULL
*
* Initializes the newly allocated @colorop_state with default
- * values. This is useful for drivers that subclass the CRTC state.
+ * values. This is useful for drivers that subclass the colorop state.
*/
static void __drm_colorop_state_reset(struct drm_colorop_state *colorop_state,
struct drm_colorop *colorop)
{
u64 val;
@@ -526,14 +526,14 @@ static void __drm_colorop_state_reset(struct drm_colorop_state *colorop_state,
/**
* __drm_colorop_reset - reset state on colorop
* @colorop: drm colorop
* @colorop_state: colorop state to assign
*
- * Initializes the newly allocated @colorop_state and assigns it to
- * the &drm_crtc->state pointer of @colorop, usually required when
- * initializing the drivers or when called from the &drm_colorop_funcs.reset
- * hook.
+ * Initializes the newly allocated @colorop_state and assigns it to the
+ * &drm_colorop->state pointer of @colorop, usually required when
+ * initializing the drivers or when called from the
+ * &drm_colorop_funcs.reset hook.
*
* This is useful for drivers that subclass the colorop state.
*/
static void __drm_colorop_reset(struct drm_colorop *colorop,
struct drm_colorop_state *colorop_state)
--
2.53.0
^ permalink raw reply related
* [PATCH v9 0/9] ADF41513/ADF41510 PLL frequency synthesizers
From: Rodrigo Alencar via B4 Relay @ 2026-03-20 16:27 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar,
Krzysztof Kozlowski, Andy Shevchenko
This patch series adds support for the Analog Devices ADF41513 and ADF41510
ultralow noise PLL frequency synthesizers. These devices are designed for
implementing local oscillators (LOs) in high-frequency applications.
The ADF41513 covers frequencies from 1 GHz to 26.5 GHz, while the ADF41510
operates from 1 GHz to 10 GHz.
Key features supported by this driver:
- Integer-N and fractional-N operation modes
- High maximum PFD frequency (250 MHz integer-N, 125 MHz fractional-N)
- 25-bit fixed modulus or 49-bit variable modulus fractional modes
- Digital lock detect functionality
- Phase resync capability for consistent output phase
- Load Enable vs Reference signal syncronization
The series includes:
1. PLL driver implementation
2. Device tree bindings documentation
3. IIO ABI documentation
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
Changes in v9:
- Expose simple_strntoull() in a safer prototype instead of new kstrntoull()
- Link to v8: https://lore.kernel.org/r/20260303-adf41513-iio-driver-v8-0-8dd2417cc465@analog.com
Changes in v8:
- Add new function kstrntoull() to lib/kstrtox.c and tests to lib/test-kstrtox.c.
- Drop custom iio u64 parser, replacing it for kstrntoull().
- Dedicated MAINTAINERS entry for drivers/iio/test/iio-test-fixpoint-parse.c.
- Link to v7: https://lore.kernel.org/r/20260216-adf41513-iio-driver-v7-0-b0ed387ab559@analog.com
Changes in v7:
- Addressed minor suggestions.
- frequency_resolution ABI for AD4350 removed in favor of generic one.
- Link to v6: https://lore.kernel.org/r/20260130-adf41513-iio-driver-v6-0-cf46239026bc@analog.com
Changes in v6:
- Drop usage of simple_strtoull().
- Implement better overflow checks with iio_safe_strntou64().
- Link to v5: https://lore.kernel.org/r/20260123-adf41513-iio-driver-v5-0-2dce812a2dda@analog.com
Changes in v5:
- Drop local parsing of 64-bit plus fractional parts
- Add iio_str_to_fixpoint64() to iio core with parsing tests
- Add DT property dependency for adi,charge-pump-resistor-ohms
- Add local definition for ADF41513_HZ_PER_GHZ and drop units.h patch
- Link to v4: https://lore.kernel.org/r/20260116-adf41513-iio-driver-v4-0-dbb7d6782217@analog.com
Changes in v4:
- Proper usage of units.h macros
- Simplifications to DT property parsing
- Adjustments to return value handling
- Drop of simple DT property node example
- Link to v3: https://lore.kernel.org/r/20260108-adf41513-iio-driver-v3-0-23d1371aef48@analog.com
Changes in v3:
- Use FIELD_MODIFY macro in driver implementation
- Drop refin_frequency iio attribute
- Drop muxout-select property from dt-bindings (and rename logic-level property)
- Use -mhz suffix in power-up frequency property
- Address documentation issues
- Link to v2: https://lore.kernel.org/r/20251219-adf41513-iio-driver-v2-0-be29a83d5793@analog.com
Changes in v2:
- separate driver implementation from extra features and improve commit messages
- use macros from units.h
- explanation of custom parse function: adf41513_parse_uhz
- reorganize driver data structures
- drop clock framework support for now
- reorganize documentation
- Link to v1: https://lore.kernel.org/r/20251110-adf41513-iio-driver-v1-0-2df8be0fdc6e@analog.com
---
Rodrigo Alencar (9):
dt-bindings: iio: frequency: add adf41513
lib: vsprintf: export simple_strntoull() in a safe prototype
iio: core: add fixed point parsing with 64-bit parts
iio: test: add kunit test for fixed-point parsing
iio: frequency: adf41513: driver implementation
iio: frequency: adf41513: handle LE synchronization feature
iio: frequency: adf41513: features on frequency change
docs: iio: add documentation for adf41513 driver
Documentation: ABI: testing: add common ABI file for iio/frequency
Documentation/ABI/testing/sysfs-bus-iio-frequency | 11 +
.../ABI/testing/sysfs-bus-iio-frequency-adf4350 | 10 -
.../bindings/iio/frequency/adi,adf41513.yaml | 215 ++++
Documentation/iio/adf41513.rst | 199 ++++
Documentation/iio/index.rst | 1 +
MAINTAINERS | 15 +
drivers/iio/frequency/Kconfig | 10 +
drivers/iio/frequency/Makefile | 1 +
drivers/iio/frequency/adf41513.c | 1240 ++++++++++++++++++++
drivers/iio/industrialio-core.c | 165 ++-
drivers/iio/test/Kconfig | 12 +
drivers/iio/test/Makefile | 1 +
drivers/iio/test/iio-test-fixpoint-parse.c | 470 ++++++++
include/linux/iio/iio.h | 2 +
include/linux/kstrtox.h | 4 +
lib/vsprintf.c | 59 +-
16 files changed, 2346 insertions(+), 69 deletions(-)
---
base-commit: ff0843ceb1fb11a6b73e0e77b932ef7967aecd4b
change-id: 20251110-adf41513-iio-driver-aaca8a7f808e
Best regards,
--
Rodrigo Alencar <rodrigo.alencar@analog.com>
^ permalink raw reply
* [PATCH v9 1/9] dt-bindings: iio: frequency: add adf41513
From: Rodrigo Alencar via B4 Relay @ 2026-03-20 16:27 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar,
Krzysztof Kozlowski
In-Reply-To: <20260320-adf41513-iio-driver-v9-0-132f0d076374@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
DT-bindings for ADF41513, an ultralow noise PLL frequency synthesizer that
can be used to implement local oscillators (LOs) as high as 26.5 GHz.
Some properties are based upon an existing PLL device properties
(e.g. ADF4350).
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
.../bindings/iio/frequency/adi,adf41513.yaml | 215 +++++++++++++++++++++
MAINTAINERS | 7 +
2 files changed, 222 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/frequency/adi,adf41513.yaml b/Documentation/devicetree/bindings/iio/frequency/adi,adf41513.yaml
new file mode 100644
index 000000000000..2d09cb94b6ff
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/frequency/adi,adf41513.yaml
@@ -0,0 +1,215 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/frequency/adi,adf41513.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices ADF41513 PLL Frequency Synthesizer
+
+maintainers:
+ - Rodrigo Alencar <rodrigo.alencar@analog.com>
+
+description:
+ The ADF41513 is an ultralow noise frequency synthesizer that can be used to
+ implement local oscillators (LOs) as high as 26.5 GHz in the upconversion and
+ downconversion sections of wireless receivers and transmitters. The ADF41510
+ supports frequencies up to 10 GHz.
+
+ https://www.analog.com/en/products/adf41510.html
+ https://www.analog.com/en/products/adf41513.html
+
+$ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+ compatible:
+ enum:
+ - adi,adf41510
+ - adi,adf41513
+
+ reg:
+ maxItems: 1
+
+ spi-max-frequency:
+ maximum: 25000000
+
+ clocks:
+ maxItems: 1
+ description: Clock that provides the reference input frequency.
+
+ avdd1-supply:
+ description: PFD and Up and Down Digital Driver Power Supply (3.3 V)
+
+ avdd2-supply:
+ description: RF Buffer and Prescaler Power Supply (3.3 V)
+
+ avdd3-supply:
+ description: N Divider Power Supply (3.3 V)
+
+ avdd4-supply:
+ description: R Divider and Lock Detector Power Supply (3.3 V)
+
+ avdd5-supply:
+ description: Sigma-Delta Modulator and SPI Power Supply (3.3 V)
+
+ vp-supply:
+ description: Charge Pump Power Supply (3.3 V)
+
+ enable-gpios:
+ description:
+ GPIO that controls the chip enable pin. A logic low on this pin
+ powers down the device and puts the charge pump output into
+ three-state mode.
+ maxItems: 1
+
+ lock-detect-gpios:
+ description:
+ GPIO for lock detect functionality. When configured for digital lock
+ detect, this pin will output a logic high when the PLL is locked.
+ maxItems: 1
+
+ adi,power-up-frequency-mhz:
+ minimum: 1000
+ maximum: 26500
+ default: 10000
+ description:
+ The PLL tunes to this frequency during the initialization sequence.
+ This property should be set to a frequency supported by the loop filter
+ and VCO used in the design. Range is 1 GHz to 26.5 GHz for ADF41513,
+ and 1 GHz to 10 GHz for ADF41510.
+
+ adi,reference-div-factor:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ minimum: 1
+ maximum: 32
+ default: 1
+ description:
+ Value for the reference division factor (R Counter). The driver will
+ increment R Counter as needed to achieve a PFD frequency within the
+ allowed range. High R counter values will reduce the PFD frequency, which
+ lowers the frequency resolution, and affects phase noise performance.
+ As it affects the PFD frequency, this value depends on the loop filter
+ design.
+
+ adi,reference-doubler-enable:
+ description:
+ Enables the reference doubler when deriving the PFD frequency.
+ The maximum reference frequency when the doubler is enabled is 225 MHz.
+ As it affects the PFD frequency, this value depends on the loop filter
+ design.
+ type: boolean
+
+ adi,reference-div2-enable:
+ description:
+ Enables the reference divide-by-2 function when deriving the PFD
+ frequency. As it affects the PFD frequency, this value depends on the
+ loop filter design.
+ type: boolean
+
+ adi,charge-pump-resistor-ohms:
+ minimum: 1800
+ maximum: 10000
+ default: 2700
+ description:
+ External charge pump resistor (R_SET) value in ohms. This sets the maximum
+ charge pump current along with the charge pump current setting.
+
+ adi,charge-pump-current-microamp:
+ description:
+ Charge pump current (I_CP) in microamps. The value will be rounded to the
+ nearest supported value. Range of acceptable values depends on the
+ charge pump resistor value, such that 810 mV <= I_CP * R_SET <= 12960 mV.
+ This value depends on the loop filter and the VCO design.
+
+ adi,logic-level-1v8-enable:
+ description:
+ Set MUXOUT and DLD logic levels to 1.8V. Default is 3.3V.
+ type: boolean
+
+ adi,phase-detector-polarity-positive-enable:
+ description:
+ Set phase detector polarity to positive. Default is negative.
+ Use positive polarity with non-inverting loop filter and VCO with
+ positive tuning slope, or with inverting loop filter and VCO with
+ negative tuning slope.
+ type: boolean
+
+ adi,lock-detector-count:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ default: 64
+ description:
+ Sets the value for Lock Detector count of the PLL, which determines the
+ number of consecutive phase detector cycles that must be within the lock
+ detector window before lock is declared. Lower values increase the lock
+ detection sensitivity, while higher values provides a more stable lock
+ detection. Applications that consume the lock detect signal may require
+ different settings based on system requirements.
+ enum: [2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192]
+
+ adi,phase-resync-period-ns:
+ default: 0
+ description:
+ When this value is non-zero, enable phase resync functionality, which
+ produces a consistent output phase offset with respect to the input
+ reference. The value specifies the resync period in nanoseconds, used
+ to configure clock dividers with respect to the PFD frequency. This value
+ should be set to a value that is at least as long as the worst case lock
+ time, i.e., it depends mostly on the loop filter design.
+
+ adi,le-sync-enable:
+ description:
+ Synchronizes Load Enable (LE) transitions with the reference signal to
+ avoid asynchronous glitches in the output. This is recommended when using
+ the PLL as a frequency synthesizer, where the reference signal will always
+ be present while the device is being configured. When using the PLL as a
+ frequency tracker, where the reference signal may be absent, LE sync
+ should be left disabled.
+ type: boolean
+
+dependencies:
+ adi,charge-pump-resistor-ohms: [ 'adi,charge-pump-current-microamp' ]
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - avdd1-supply
+ - avdd2-supply
+ - avdd3-supply
+ - avdd4-supply
+ - avdd5-supply
+ - vp-supply
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pll@0 {
+ compatible = "adi,adf41513";
+ reg = <0>;
+ spi-max-frequency = <25000000>;
+ clocks = <&ref_clk>;
+ avdd1-supply = <&avdd1_3v3>;
+ avdd2-supply = <&avdd2_3v3>;
+ avdd3-supply = <&avdd3_3v3>;
+ avdd4-supply = <&avdd4_3v3>;
+ avdd5-supply = <&avdd5_3v3>;
+ vp-supply = <&vp_3v3>;
+ enable-gpios = <&gpio0 10 GPIO_ACTIVE_HIGH>;
+ lock-detect-gpios = <&gpio0 11 GPIO_ACTIVE_HIGH>;
+
+ adi,power-up-frequency-mhz = <15500>;
+ adi,charge-pump-current-microamp = <3600>;
+ adi,charge-pump-resistor-ohms = <2700>;
+ adi,reference-doubler-enable;
+ adi,lock-detector-count = <64>;
+ adi,phase-resync-period-ns = <0>;
+ adi,phase-detector-polarity-positive-enable;
+ adi,le-sync-enable;
+ };
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 08d8ddf4ef68..e3568f08ea55 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1654,6 +1654,13 @@ W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml
F: drivers/iio/adc/ade9000.c
+ANALOG DEVICES INC ADF41513 DRIVER
+M: Rodrigo Alencar <rodrigo.alencar@analog.com>
+L: linux-iio@vger.kernel.org
+S: Supported
+W: https://ez.analog.com/linux-software-drivers
+F: Documentation/devicetree/bindings/iio/frequency/adi,adf41513.yaml
+
ANALOG DEVICES INC ADF4377 DRIVER
M: Antoniu Miclaus <antoniu.miclaus@analog.com>
L: linux-iio@vger.kernel.org
--
2.43.0
^ permalink raw reply related
* [PATCH v9 2/9] lib: vsprintf: export simple_strntoull() in a safe prototype
From: Rodrigo Alencar via B4 Relay @ 2026-03-20 16:27 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar
In-Reply-To: <20260320-adf41513-iio-driver-v9-0-132f0d076374@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Expose simple_strntoull(), by addressing its FIXME, i.e. its prototype is
slightly changed so that -ERANGE or -EINVAL can be evaluated by the user.
Flow of the function is not changed and error value is returned in the
end. Unsafe internal wrapper is created to reduce amount of changes.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
include/linux/kstrtox.h | 4 ++++
lib/vsprintf.c | 59 +++++++++++++++++++++++++++++++++++++++++--------
2 files changed, 54 insertions(+), 9 deletions(-)
diff --git a/include/linux/kstrtox.h b/include/linux/kstrtox.h
index 6ea897222af1..5e161073121f 100644
--- a/include/linux/kstrtox.h
+++ b/include/linux/kstrtox.h
@@ -148,4 +148,8 @@ extern long simple_strtol(const char *,char **,unsigned int);
extern unsigned long long simple_strtoull(const char *,char **,unsigned int);
extern long long simple_strtoll(const char *,char **,unsigned int);
+extern ssize_t __must_check simple_strntoull(const char *startp, const char **endp,
+ unsigned int base, size_t max_chars,
+ unsigned long long *res);
+
#endif /* _LINUX_KSTRTOX_H */
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index 800b8ac49f53..6fb880f4013b 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -75,25 +75,66 @@ enum hash_pointers_policy {
};
static enum hash_pointers_policy hash_pointers_mode __initdata;
+/**
+ * simple_strntoull - convert a string to an unsigned long long with a character limit
+ *
+ * @startp: The start of the string
+ * @endp: A pointer to the end of the parsed string will be placed here
+ * @base: The number base to use
+ * @max_chars: The maximum number of characters to parse
+ * @res: Where to write the result of the conversion on success
+ *
+ * Returns amount of processed characters on success, -ERANGE on overflow and
+ * -EINVAL on parsing error.
+ */
noinline
-static unsigned long long simple_strntoull(const char *startp, char **endp, unsigned int base, size_t max_chars)
+ssize_t simple_strntoull(const char *startp, const char **endp,
+ unsigned int base, size_t max_chars,
+ unsigned long long *res)
{
const char *cp;
- unsigned long long result = 0ULL;
size_t prefix_chars;
unsigned int rv;
+ ssize_t ret;
cp = _parse_integer_fixup_radix(startp, &base);
prefix_chars = cp - startp;
if (prefix_chars < max_chars) {
- rv = _parse_integer_limit(cp, base, &result, max_chars - prefix_chars);
- /* FIXME */
+ rv = _parse_integer_limit(cp, base, res, max_chars - prefix_chars);
+ if (rv & KSTRTOX_OVERFLOW)
+ ret = -ERANGE;
+ else if (rv == 0)
+ ret = -EINVAL;
+ else
+ ret = rv + prefix_chars;
cp += (rv & ~KSTRTOX_OVERFLOW);
} else {
/* Field too short for prefix + digit, skip over without converting */
cp = startp + max_chars;
+ ret = -EINVAL;
+ *res = 0ULL;
}
+ if (endp)
+ *endp = cp;
+
+ return ret;
+}
+EXPORT_SYMBOL(simple_strntoull);
+
+/* unsafe_strntoull ignores simple_strntoull() return value and endp const qualifier */
+inline
+static unsigned long long unsafe_strntoull(const char *startp, char **endp,
+ unsigned int base, size_t max_chars)
+{
+ unsigned long long result;
+ const char *cp;
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-result"
+ simple_strntoull(startp, &cp, base, max_chars, &result);
+#pragma GCC diagnostic pop
+
if (endp)
*endp = (char *)cp;
@@ -111,7 +152,7 @@ static unsigned long long simple_strntoull(const char *startp, char **endp, unsi
noinline
unsigned long long simple_strtoull(const char *cp, char **endp, unsigned int base)
{
- return simple_strntoull(cp, endp, base, INT_MAX);
+ return unsafe_strntoull(cp, endp, base, INT_MAX);
}
EXPORT_SYMBOL(simple_strtoull);
@@ -132,7 +173,7 @@ EXPORT_SYMBOL(simple_strtoul);
unsigned long simple_strntoul(const char *cp, char **endp, unsigned int base,
size_t max_chars)
{
- return simple_strntoull(cp, endp, base, max_chars);
+ return unsafe_strntoull(cp, endp, base, max_chars);
}
EXPORT_SYMBOL(simple_strntoul);
@@ -163,9 +204,9 @@ static long long simple_strntoll(const char *cp, char **endp, unsigned int base,
* and the content of *cp is irrelevant.
*/
if (*cp == '-' && max_chars > 0)
- return -simple_strntoull(cp + 1, endp, base, max_chars - 1);
+ return -unsafe_strntoull(cp + 1, endp, base, max_chars - 1);
- return simple_strntoull(cp, endp, base, max_chars);
+ return unsafe_strntoull(cp, endp, base, max_chars);
}
/**
@@ -3670,7 +3711,7 @@ int vsscanf(const char *buf, const char *fmt, va_list args)
val.s = simple_strntoll(str, &next, base,
field_width >= 0 ? field_width : INT_MAX);
else
- val.u = simple_strntoull(str, &next, base,
+ val.u = unsafe_strntoull(str, &next, base,
field_width >= 0 ? field_width : INT_MAX);
switch (qualifier) {
--
2.43.0
^ permalink raw reply related
* [PATCH v9 3/9] iio: core: add fixed point parsing with 64-bit parts
From: Rodrigo Alencar via B4 Relay @ 2026-03-20 16:27 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar
In-Reply-To: <20260320-adf41513-iio-driver-v9-0-132f0d076374@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add iio_str_to_fixpoint64() function that leverages simple_strntoull()
to parse numbers from a string. A helper function
__iio_str_to_fixpoint64() replaces __iio_str_to_fixpoint() implementation,
extending its usage for 64-bit fixed-point parsing.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/industrialio-core.c | 165 ++++++++++++++++++++++++++++------------
include/linux/iio/iio.h | 2 +
2 files changed, 117 insertions(+), 50 deletions(-)
diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c
index 22eefd048ba9..69cb0355526c 100644
--- a/drivers/iio/industrialio-core.c
+++ b/drivers/iio/industrialio-core.c
@@ -881,6 +881,91 @@ static ssize_t iio_read_channel_info_avail(struct device *dev,
}
}
+/**
+ * __iio_str_to_fixpoint64() - Parse a fixed-point number from a string
+ * @str: The string to parse
+ * @fract_mult: Multiplier for the first decimal place, should be a power of 10
+ * @integer: The integer part of the number
+ * @fract: The fractional part of the number
+ * @scale_db: True if this should parse as dB
+ *
+ * This variant uses 64-bit integers for both integer and fractional parts.
+ * Parsed positive values greater than S64_MAX are returned as-is. Parsed
+ * negative values less than S64_MIN are treated as range error, so -ERANGE is
+ * returned.
+ *
+ * Returns:
+ * 0 on success, or a negative error code if the string could not be parsed.
+ */
+static int __iio_str_to_fixpoint64(const char *str, u64 fract_mult,
+ s64 *integer, s64 *fract, bool scale_db)
+{
+ u64 i = 0, f = 0;
+ int precision = ffs(fract_mult);
+ bool negative = false;
+ ssize_t len;
+
+ if (precision > 20) /* ceil(log10(U64_MAX)) = 20 */
+ return -EINVAL;
+
+ if (str[0] == '-') {
+ negative = true;
+ str++;
+ } else if (str[0] == '+') {
+ str++;
+ }
+
+ if (*str != '.') {
+ len = simple_strntoull(str, &str, 10, SIZE_MAX, &i);
+ if (len < 0)
+ return len;
+ }
+
+ if (precision && *str == '.') {
+ str++; /* skip decimal point */
+ len = simple_strntoull(str, &str, 10, precision, &f);
+ if (len < 0)
+ return len;
+
+ if (len < precision) /* scale up */
+ f *= int_pow(10, precision - len);
+
+ while (isdigit(*str)) /* truncate: ignore further digits */
+ str++;
+ }
+
+ if (scale_db) {
+ /* Ignore the dB suffix */
+ if (!strncmp(str, " dB", sizeof(" dB") - 1))
+ str += sizeof(" dB") - 1;
+ else if (!strncmp(str, "dB", sizeof("dB") - 1))
+ str += sizeof("dB") - 1;
+ }
+
+ if (*str == '\n')
+ str++;
+
+ if (*str != '\0')
+ return -EINVAL;
+
+ if (negative) {
+ if (i) {
+ if ((s64)-i > 0)
+ return -ERANGE;
+ i = -i;
+ } else {
+ if ((s64)-f > 0)
+ return -ERANGE;
+ f = -f;
+ }
+ }
+
+ *integer = i;
+ *fract = f;
+
+ return 0;
+}
+
/**
* __iio_str_to_fixpoint() - Parse a fixed-point number from a string
* @str: The string to parse
@@ -895,63 +980,43 @@ static ssize_t iio_read_channel_info_avail(struct device *dev,
static int __iio_str_to_fixpoint(const char *str, int fract_mult,
int *integer, int *fract, bool scale_db)
{
- int i = 0, f = 0;
- bool integer_part = true, negative = false;
+ s64 integer64, fract64;
+ int ret;
- if (fract_mult == 0) {
- *fract = 0;
+ ret = __iio_str_to_fixpoint64(str, fract_mult, &integer64, &fract64,
+ scale_db);
+ if (ret)
+ return ret;
- return kstrtoint(str, 0, integer);
- }
+ if (integer64 < INT_MIN || integer64 > UINT_MAX ||
+ fract64 < INT_MIN || fract64 > UINT_MAX)
+ return -ERANGE;
- if (str[0] == '-') {
- negative = true;
- str++;
- } else if (str[0] == '+') {
- str++;
- }
-
- while (*str) {
- if ('0' <= *str && *str <= '9') {
- if (integer_part) {
- i = i * 10 + *str - '0';
- } else {
- f += fract_mult * (*str - '0');
- fract_mult /= 10;
- }
- } else if (*str == '\n') {
- if (*(str + 1) == '\0')
- break;
- return -EINVAL;
- } else if (!strncmp(str, " dB", sizeof(" dB") - 1) && scale_db) {
- /* Ignore the dB suffix */
- str += sizeof(" dB") - 1;
- continue;
- } else if (!strncmp(str, "dB", sizeof("dB") - 1) && scale_db) {
- /* Ignore the dB suffix */
- str += sizeof("dB") - 1;
- continue;
- } else if (*str == '.' && integer_part) {
- integer_part = false;
- } else {
- return -EINVAL;
- }
- str++;
- }
-
- if (negative) {
- if (i)
- i = -i;
- else
- f = -f;
- }
-
- *integer = i;
- *fract = f;
+ *integer = integer64;
+ *fract = fract64;
return 0;
}
+/**
+ * iio_str_to_fixpoint64() - Parse a fixed-point number from a string
+ * @str: The string to parse
+ * @fract_mult: Multiplier for the first decimal place, should be a power of 10
+ * @integer: The integer part of the number
+ * @fract: The fractional part of the number
+ *
+ * This variant uses 64-bit integers for both integer and fractional parts.
+ *
+ * Returns:
+ * 0 on success, or a negative error code if the string could not be parsed.
+ */
+int iio_str_to_fixpoint64(const char *str, u64 fract_mult, s64 *integer,
+ s64 *fract)
+{
+ return __iio_str_to_fixpoint64(str, fract_mult, integer, fract, false);
+}
+EXPORT_SYMBOL_GPL(iio_str_to_fixpoint64);
+
/**
* iio_str_to_fixpoint() - Parse a fixed-point number from a string
* @str: The string to parse
diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h
index a9ecff191bd9..cb30d153465a 100644
--- a/include/linux/iio/iio.h
+++ b/include/linux/iio/iio.h
@@ -1055,6 +1055,8 @@ int iio_active_scan_mask_index(struct iio_dev *indio_dev);
ssize_t iio_format_value(char *buf, unsigned int type, int size, int *vals);
+int iio_str_to_fixpoint64(const char *str, u64 fract_mult, s64 *integer,
+ s64 *fract);
int iio_str_to_fixpoint(const char *str, int fract_mult, int *integer,
int *fract);
--
2.43.0
^ permalink raw reply related
* [PATCH v9 4/9] iio: test: add kunit test for fixed-point parsing
From: Rodrigo Alencar via B4 Relay @ 2026-03-20 16:27 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar, Andy Shevchenko
In-Reply-To: <20260320-adf41513-iio-driver-v9-0-132f0d076374@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add kunit test cases that aims to verify expected behavior for
iio_str_to_fixpoint() and iio_str_to_fixpoint64().
To run the test, create a .kunitconfig file with:
CONFIG_KUNIT=y
CONFIG_IIO=y
CONFIG_IIO_FIXPOINT_PARSE_KUNIT_TEST=y
and run the command:
./tools/testing/kunit/kunit.py run --kunitconfig=.kunitconfig
Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
MAINTAINERS | 6 +
drivers/iio/test/Kconfig | 12 +
drivers/iio/test/Makefile | 1 +
drivers/iio/test/iio-test-fixpoint-parse.c | 470 +++++++++++++++++++++++++++++
4 files changed, 489 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index e3568f08ea55..3acfee7a66cd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12455,6 +12455,12 @@ F: include/dt-bindings/iio/
F: include/linux/iio/
F: tools/iio/
+IIO FIXED POINT PARSE TEST
+M: Rodrigo Alencar <rodrigo.alencar@analog.com>
+L: linux-iio@vger.kernel.org
+S: Maintained
+F: drivers/iio/test/iio-test-fixpoint-parse.c
+
IIO UNIT CONVERTER
M: Peter Rosin <peda@axentia.se>
L: linux-iio@vger.kernel.org
diff --git a/drivers/iio/test/Kconfig b/drivers/iio/test/Kconfig
index 4fc17dd0dcd7..c60dff504bc2 100644
--- a/drivers/iio/test/Kconfig
+++ b/drivers/iio/test/Kconfig
@@ -29,6 +29,18 @@ config IIO_RESCALE_KUNIT_TEST
If unsure, say N.
+config IIO_FIXPOINT_PARSE_KUNIT_TEST
+ tristate "Test IIO fixpoint parsing functions" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ build unit tests for the IIO fixpoint parsing functions.
+
+ For more information on KUnit and unit tests in general, please refer
+ to the KUnit documentation in Documentation/dev-tools/kunit/.
+
+ If unsure, say N.
+
config IIO_FORMAT_KUNIT_TEST
tristate "Test IIO formatting functions" if !KUNIT_ALL_TESTS
depends on KUNIT
diff --git a/drivers/iio/test/Makefile b/drivers/iio/test/Makefile
index 0c846bc21acd..0c31aaeed755 100644
--- a/drivers/iio/test/Makefile
+++ b/drivers/iio/test/Makefile
@@ -5,6 +5,7 @@
# Keep in alphabetical order
obj-$(CONFIG_IIO_RESCALE_KUNIT_TEST) += iio-test-rescale.o
+obj-$(CONFIG_IIO_FIXPOINT_PARSE_KUNIT_TEST) += iio-test-fixpoint-parse.o
obj-$(CONFIG_IIO_FORMAT_KUNIT_TEST) += iio-test-format.o
obj-$(CONFIG_IIO_GTS_KUNIT_TEST) += iio-test-gts.o
obj-$(CONFIG_IIO_MULTIPLY_KUNIT_TEST) += iio-test-multiply.o
diff --git a/drivers/iio/test/iio-test-fixpoint-parse.c b/drivers/iio/test/iio-test-fixpoint-parse.c
new file mode 100644
index 000000000000..69715d22d7ec
--- /dev/null
+++ b/drivers/iio/test/iio-test-fixpoint-parse.c
@@ -0,0 +1,470 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Unit tests for IIO fixpoint parsing functions
+ *
+ * Copyright 2026 Analog Devices Inc.
+ */
+
+#include <kunit/test.h>
+#include <linux/errno.h>
+#include <linux/iio/iio.h>
+#include <linux/limits.h>
+#include <linux/math.h>
+#include <linux/types.h>
+
+#define PRECISION(x) (int_pow(10, (x) - 1))
+
+/* Test iio_str_to_fixpoint64() with valid positive integers */
+static void iio_test_str_to_fixpoint64_positive_integers(struct kunit *test)
+{
+ s64 integer, fract;
+ int ret;
+
+ /* Simple positive integer */
+ ret = iio_str_to_fixpoint64("42", 0, &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 42);
+ KUNIT_EXPECT_EQ(test, fract, 0);
+
+ /* Positive integer with leading + */
+ ret = iio_str_to_fixpoint64("+10", 0, &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 10);
+ KUNIT_EXPECT_EQ(test, fract, 0);
+
+ /* Large positive integer */
+ ret = iio_str_to_fixpoint64("123456789", 0, &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 123456789);
+ KUNIT_EXPECT_EQ(test, fract, 0);
+}
+
+/* Test iio_str_to_fixpoint64() with valid negative integers */
+static void iio_test_str_to_fixpoint64_negative_integers(struct kunit *test)
+{
+ s64 integer, fract;
+ int ret;
+
+ /* Simple negative integer */
+ ret = iio_str_to_fixpoint64("-23", 0, &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, -23);
+ KUNIT_EXPECT_EQ(test, fract, 0);
+
+ /* Large negative integer */
+ ret = iio_str_to_fixpoint64("-987654321", 0, &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, -987654321);
+ KUNIT_EXPECT_EQ(test, fract, 0);
+}
+
+/* Test iio_str_to_fixpoint64() with zero */
+static void iio_test_str_to_fixpoint64_zero(struct kunit *test)
+{
+ s64 integer, fract;
+ int ret;
+
+ /* Zero */
+ ret = iio_str_to_fixpoint64("0", 0, &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 0);
+ KUNIT_EXPECT_EQ(test, fract, 0);
+
+ /* Zero with decimal */
+ ret = iio_str_to_fixpoint64("0.0", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 0);
+ KUNIT_EXPECT_EQ(test, fract, 0);
+
+ /* leading zeros */
+ ret = iio_str_to_fixpoint64("00000000000000000000042", 0, &integer,
+ &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 42);
+ KUNIT_EXPECT_EQ(test, fract, 0);
+}
+
+/* Test iio_str_to_fixpoint64() with valid decimal numbers */
+static void iio_test_str_to_fixpoint64_positive_decimals(struct kunit *test)
+{
+ s64 integer, fract;
+ int ret;
+
+ /* Positive decimal */
+ ret = iio_str_to_fixpoint64("3.14", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 3);
+ KUNIT_EXPECT_EQ(test, fract, 140000);
+
+ /* Decimal less than 1 */
+ ret = iio_str_to_fixpoint64("0.5", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 0);
+ KUNIT_EXPECT_EQ(test, fract, 500000);
+
+ /* Decimal with trailing zeros */
+ ret = iio_str_to_fixpoint64("+123.000", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 123);
+ KUNIT_EXPECT_EQ(test, fract, 0);
+
+ /* High precision decimal */
+ ret = iio_str_to_fixpoint64("1.123456789", PRECISION(9), &integer,
+ &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 1);
+ KUNIT_EXPECT_EQ(test, fract, 123456789);
+
+ /* Small decimal */
+ ret = iio_str_to_fixpoint64("0.000000001", PRECISION(9), &integer,
+ &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 0);
+ KUNIT_EXPECT_EQ(test, fract, 1);
+}
+
+/* Test iio_str_to_fixpoint64() with negative decimals */
+static void iio_test_str_to_fixpoint64_negative_decimals(struct kunit *test)
+{
+ s64 integer, fract;
+ int ret;
+
+ /* Negative decimal */
+ ret = iio_str_to_fixpoint64("-2.71", PRECISION(5), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, -2);
+ KUNIT_EXPECT_EQ(test, fract, 71000);
+
+ /* Negative decimal less than -1 */
+ ret = iio_str_to_fixpoint64("-0.5", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 0);
+ KUNIT_EXPECT_EQ(test, fract, -500000);
+
+ /* Negative with high precision */
+ ret = iio_str_to_fixpoint64("-0.000000001", PRECISION(9), &integer,
+ &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 0);
+ KUNIT_EXPECT_EQ(test, fract, -1);
+}
+
+/* Test iio_str_to_fixpoint64() with precision edge cases */
+static void iio_test_str_to_fixpoint64_precision_edge_cases(struct kunit *test)
+{
+ s64 integer, fract;
+ int ret;
+
+ /* More digits than precision - should truncate */
+ ret = iio_str_to_fixpoint64("1.23456", PRECISION(3), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 1);
+ KUNIT_EXPECT_EQ(test, fract, 234);
+
+ /* Fewer digits than precision - should pad with zeros */
+ ret = iio_str_to_fixpoint64("1.23", PRECISION(7), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 1);
+ KUNIT_EXPECT_EQ(test, fract, 2300000);
+
+ /* Single digit fractional with high precision */
+ ret = iio_str_to_fixpoint64("5.1", PRECISION(9), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 5);
+ KUNIT_EXPECT_EQ(test, fract, 100000000);
+}
+
+/* Test iio_str_to_fixpoint64() with newline characters */
+static void iio_test_str_to_fixpoint64_with_newline(struct kunit *test)
+{
+ s64 integer, fract;
+ int ret;
+
+ /* Integer with newline */
+ ret = iio_str_to_fixpoint64("-42\n", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, -42);
+ KUNIT_EXPECT_EQ(test, fract, 0);
+
+ /* Decimal with newline */
+ ret = iio_str_to_fixpoint64("3.141\n", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 3);
+ KUNIT_EXPECT_EQ(test, fract, 141000);
+}
+
+/* Test iio_str_to_fixpoint64() with edge cases */
+static void iio_test_str_to_fixpoint64_edge_cases(struct kunit *test)
+{
+ s64 integer, fract;
+ int ret;
+
+ /* Leading decimal point */
+ ret = iio_str_to_fixpoint64(".5", PRECISION(4), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 0);
+ KUNIT_EXPECT_EQ(test, fract, 5000);
+
+ /* Leading decimal with sign */
+ ret = iio_str_to_fixpoint64("-.5", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 0);
+ KUNIT_EXPECT_EQ(test, fract, -500000);
+
+ ret = iio_str_to_fixpoint64("+.5", PRECISION(3), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 0);
+ KUNIT_EXPECT_EQ(test, fract, 500);
+}
+
+/* Test iio_str_to_fixpoint64() with invalid inputs */
+static void iio_test_str_to_fixpoint64_invalid(struct kunit *test)
+{
+ s64 integer, fract;
+ int ret;
+
+ /* Empty string */
+ ret = iio_str_to_fixpoint64("", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ /* Just a sign */
+ ret = iio_str_to_fixpoint64("-", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ ret = iio_str_to_fixpoint64("+", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ /* Just a decimal point */
+ ret = iio_str_to_fixpoint64(".", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ /* Non-numeric characters */
+ ret = iio_str_to_fixpoint64("abc", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ ret = iio_str_to_fixpoint64("12a", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ ret = iio_str_to_fixpoint64("3.4x", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ ret = iio_str_to_fixpoint64("0xff", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ /* Multiple decimal points */
+ ret = iio_str_to_fixpoint64("12.34.56", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ /* Trailing decimal without digits */
+ ret = iio_str_to_fixpoint64("42.", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ /* Trailing spaces */
+ ret = iio_str_to_fixpoint64("42 ", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ /* Too many digits in fractional part */
+ ret = iio_str_to_fixpoint64("1.123456789012345678901", PRECISION(21),
+ &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL); /* fails when checking precision */
+}
+
+/* Test iio_str_to_fixpoint() with valid inputs */
+static void iio_test_str_to_fixpoint_valid(struct kunit *test)
+{
+ int integer, fract;
+ int ret;
+
+ /* Test with 6 decimal places */
+ ret = iio_str_to_fixpoint("10.001234", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 10);
+ KUNIT_EXPECT_EQ(test, fract, 1234);
+
+ ret = iio_str_to_fixpoint("-0.5", PRECISION(3), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 0);
+ KUNIT_EXPECT_EQ(test, fract, -500);
+
+ /* Test with 9 decimal places */
+ ret = iio_str_to_fixpoint("5.123456789", PRECISION(9), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 5);
+ KUNIT_EXPECT_EQ(test, fract, 123456789);
+
+ ret = iio_str_to_fixpoint("1.0", PRECISION(9), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 1);
+ KUNIT_EXPECT_EQ(test, fract, 0);
+
+ /* Test with 3 decimal places */
+ ret = iio_str_to_fixpoint("-7.8", PRECISION(3), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, -7);
+ KUNIT_EXPECT_EQ(test, fract, 800);
+
+ /* Truncation with 2 decimal places */
+ ret = iio_str_to_fixpoint("3.1415", PRECISION(2), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 3);
+ KUNIT_EXPECT_EQ(test, fract, 14);
+
+ /* Integer with 6 decimal places */
+ ret = iio_str_to_fixpoint("42", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer, 42);
+ KUNIT_EXPECT_EQ(test, fract, 0);
+}
+
+/* Test both functions with overflow cases */
+static void iio_test_str_to_fixpoint_overflow(struct kunit *test)
+{
+ s64 integer64, fract64;
+ int integer, fract;
+ int ret;
+
+ /* integer overflow - value exceeds U64_MAX */
+ ret = iio_str_to_fixpoint64("18446744073709551616", PRECISION(6),
+ &integer64, &fract64);
+ KUNIT_EXPECT_EQ(test, ret, -ERANGE);
+
+ /* integer underflow - value less than S64_MIN */
+ ret = iio_str_to_fixpoint64("-9223372036854775809", PRECISION(6),
+ &integer64, &fract64);
+ KUNIT_EXPECT_EQ(test, ret, -ERANGE);
+
+ /* fractional underflow */
+ ret = iio_str_to_fixpoint64("-0.9223372036854775810", PRECISION(19),
+ &integer64, &fract64);
+ KUNIT_EXPECT_EQ(test, ret, -ERANGE);
+
+ /* Integer overflow - value exceeds U32_MAX */
+ ret = iio_str_to_fixpoint("4294967296", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, -ERANGE);
+
+ /* Integer underflow - value less than INT_MIN */
+ ret = iio_str_to_fixpoint("-2147483649", PRECISION(6), &integer,
+ &fract);
+ KUNIT_EXPECT_EQ(test, ret, -ERANGE);
+
+ /* fractional overflow */
+ ret = iio_str_to_fixpoint("0.4294967296", PRECISION(10), &integer,
+ &fract);
+ KUNIT_EXPECT_EQ(test, ret, -ERANGE);
+
+ /* fractional underflow */
+ ret = iio_str_to_fixpoint("-0.2147483649", PRECISION(10), &integer,
+ &fract);
+ KUNIT_EXPECT_EQ(test, ret, -ERANGE);
+}
+
+/* Test iio_str_to_fixpoint() with invalid inputs */
+static void iio_test_str_to_fixpoint_invalid(struct kunit *test)
+{
+ int integer, fract;
+ int ret;
+
+ /* Empty string */
+ ret = iio_str_to_fixpoint("", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_NE(test, ret, 0);
+
+ /* Non-numeric */
+ ret = iio_str_to_fixpoint("abc", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_NE(test, ret, 0);
+
+ /* Invalid characters */
+ ret = iio_str_to_fixpoint("12.34x", PRECISION(6), &integer, &fract);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+}
+
+/* Test both functions with boundary values */
+static void iio_test_fixpoint_boundary_values(struct kunit *test)
+{
+ s64 integer64, fract64;
+ int integer32, fract32;
+ int ret;
+
+ /* S32_MAX */
+ ret = iio_str_to_fixpoint("2147483647", PRECISION(6), &integer32,
+ &fract32);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer32, S32_MAX);
+ KUNIT_EXPECT_EQ(test, fract32, 0);
+
+ /* U32_MAX */
+ ret = iio_str_to_fixpoint("4294967295", PRECISION(6), &integer32,
+ &fract32);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, (u32)integer32, U32_MAX);
+ KUNIT_EXPECT_EQ(test, fract32, 0);
+
+ /* S32_MIN */
+ ret = iio_str_to_fixpoint("-2147483648", PRECISION(6), &integer32,
+ &fract32);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer32, S32_MIN);
+ KUNIT_EXPECT_EQ(test, fract32, 0);
+
+ /* S32_MIN with fractional part */
+ ret = iio_str_to_fixpoint("-2147483648.2147483647", PRECISION(10),
+ &integer32, &fract32);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer32, S32_MIN);
+ KUNIT_EXPECT_EQ(test, fract32, S32_MAX);
+
+ /* S64_MAX */
+ ret = iio_str_to_fixpoint64("9223372036854775807", PRECISION(6),
+ &integer64, &fract64);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer64, S64_MAX);
+ KUNIT_EXPECT_EQ(test, fract64, 0);
+
+ /* U64_MAX */
+ ret = iio_str_to_fixpoint64("18446744073709551615", PRECISION(6),
+ &integer64, &fract64);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, (u64)integer64, U64_MAX);
+ KUNIT_EXPECT_EQ(test, fract64, 0);
+
+ /* S64_MIN */
+ ret = iio_str_to_fixpoint64("-9223372036854775808", PRECISION(6),
+ &integer64, &fract64);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer64, S64_MIN);
+ KUNIT_EXPECT_EQ(test, fract64, 0);
+
+ /* S64_MIN with fractional part */
+ ret = iio_str_to_fixpoint64("-9223372036854775808.9223372036854775807",
+ PRECISION(19), &integer64, &fract64);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, integer64, S64_MIN);
+ KUNIT_EXPECT_EQ(test, fract64, S64_MAX);
+}
+
+static struct kunit_case iio_fixpoint_parse_test_cases[] = {
+ KUNIT_CASE(iio_test_str_to_fixpoint64_positive_integers),
+ KUNIT_CASE(iio_test_str_to_fixpoint64_negative_integers),
+ KUNIT_CASE(iio_test_str_to_fixpoint64_zero),
+ KUNIT_CASE(iio_test_str_to_fixpoint64_positive_decimals),
+ KUNIT_CASE(iio_test_str_to_fixpoint64_negative_decimals),
+ KUNIT_CASE(iio_test_str_to_fixpoint64_precision_edge_cases),
+ KUNIT_CASE(iio_test_str_to_fixpoint64_with_newline),
+ KUNIT_CASE(iio_test_str_to_fixpoint64_edge_cases),
+ KUNIT_CASE(iio_test_str_to_fixpoint64_invalid),
+ KUNIT_CASE(iio_test_str_to_fixpoint_valid),
+ KUNIT_CASE(iio_test_str_to_fixpoint_overflow),
+ KUNIT_CASE(iio_test_str_to_fixpoint_invalid),
+ KUNIT_CASE(iio_test_fixpoint_boundary_values),
+ { }
+};
+
+static struct kunit_suite iio_fixpoint_parse_test_suite = {
+ .name = "iio-fixpoint-parse",
+ .test_cases = iio_fixpoint_parse_test_cases,
+};
+
+kunit_test_suite(iio_fixpoint_parse_test_suite);
+
+MODULE_AUTHOR("Rodrigo Alencar <rodrigo.alencar@analog.com>");
+MODULE_AUTHOR("IIO Kunit Test");
+MODULE_DESCRIPTION("Test IIO fixpoint parsing functions");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related
* [PATCH v2 02/20] drm/atomic: Drop drm_private_state.obj assignment from create_state
From: Maxime Ripard @ 2026-03-20 16:27 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Dmitry Baryshkov, Jyri Sarha,
Tomi Valkeinen, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Simon Ser,
Harry Wentland, Melissa Wen, Sebastian Wick, Alex Hung,
Jani Nikula, Rodrigo Vivi, Joonas Lahtinen, Tvrtko Ursulin,
Chen-Yu Tsai, Samuel Holland, Dave Stevenson, Maíra Canal,
Raspberry Pi Kernel Maintenance
Cc: dri-devel, linux-doc, linux-kernel, Daniel Stone, intel-gfx,
intel-xe, linux-arm-kernel, linux-sunxi, Maxime Ripard
In-Reply-To: <20260320-drm-mode-config-init-v2-0-c63f1134e76c@kernel.org>
The initial intent of the atomic_create_state helper was to simply
allocate a proper drm_private_state and returning it, without any side
effect.
However, the __drm_atomic_helper_private_obj_create_state() introduces a
side effect by setting the drm_private_obj.state to the newly allocated
state.
This assignment defeats the purpose, but is also redundant since
the only caller, drm_atomic_private_obj_init(), will also set this
pointer to the newly allocated state.
Let's drop the assignment in __drm_atomic_helper_private_obj_create_state().
Fixes: e7be39ed1716 ("drm/atomic-helper: Add private_obj atomic_create_state helper")
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic_state_helper.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
index 76746ad4a1bbb7142c067c93dd05fbec5d2f98eb..875149494b00e1eb7481e87d7d7038103b72b7e3 100644
--- a/drivers/gpu/drm/drm_atomic_state_helper.c
+++ b/drivers/gpu/drm/drm_atomic_state_helper.c
@@ -729,12 +729,10 @@ EXPORT_SYMBOL(drm_atomic_helper_connector_destroy_state);
void __drm_atomic_helper_private_obj_create_state(struct drm_private_obj *obj,
struct drm_private_state *state)
{
if (state)
state->obj = obj;
-
- obj->state = state;
}
EXPORT_SYMBOL(__drm_atomic_helper_private_obj_create_state);
/**
* __drm_atomic_helper_private_obj_duplicate_state - copy atomic private state
--
2.53.0
^ permalink raw reply related
* [PATCH v9 5/9] iio: frequency: adf41513: driver implementation
From: Rodrigo Alencar via B4 Relay @ 2026-03-20 16:27 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar
In-Reply-To: <20260320-adf41513-iio-driver-v9-0-132f0d076374@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
The driver is based on existing PLL drivers in the IIO subsystem and
implements the following key features:
- Integer-N and fractional-N (fixed/variable modulus) synthesis modes
- High-resolution frequency calculations using microhertz (µHz) precision
to handle sub-Hz resolution across multi-GHz frequency ranges
- IIO debugfs interface for direct register access
- FW property parsing from devicetree including charge pump settings,
reference path configuration and muxout options
- Power management support with suspend/resume callbacks
- Lock detect GPIO monitoring
The driver uses 64-bit microhertz values throughout PLL calculations to
maintain precision when working with frequencies that exceed 32-bit Hz
representation while requiring fractional Hz resolution.
When merging, ADF41513_HZ_PER_GHZ must be dropped in favor of
HZ_PER_GHZ defined in linux/units.h.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
MAINTAINERS | 1 +
drivers/iio/frequency/Kconfig | 10 +
drivers/iio/frequency/Makefile | 1 +
drivers/iio/frequency/adf41513.c | 1111 ++++++++++++++++++++++++++++++++++++++
4 files changed, 1123 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 3acfee7a66cd..fb723e40f1a9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1660,6 +1660,7 @@ L: linux-iio@vger.kernel.org
S: Supported
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/frequency/adi,adf41513.yaml
+F: drivers/iio/frequency/adf41513.c
ANALOG DEVICES INC ADF4377 DRIVER
M: Antoniu Miclaus <antoniu.miclaus@analog.com>
diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig
index 583cbdf4e8cd..90c6304c4bcd 100644
--- a/drivers/iio/frequency/Kconfig
+++ b/drivers/iio/frequency/Kconfig
@@ -29,6 +29,16 @@ endmenu
menu "Phase-Locked Loop (PLL) frequency synthesizers"
+config ADF41513
+ tristate "Analog Devices ADF41513 PLL Frequency Synthesizer"
+ depends on SPI
+ help
+ Say yes here to build support for Analog Devices ADF41513
+ 26.5 GHz Integer-N/Fractional-N PLL Frequency Synthesizer.
+
+ To compile this driver as a module, choose M here: the
+ module will be called adf41513.
+
config ADF4350
tristate "Analog Devices ADF4350/ADF4351 Wideband Synthesizers"
depends on SPI
diff --git a/drivers/iio/frequency/Makefile b/drivers/iio/frequency/Makefile
index 70d0e0b70e80..53b4d01414d8 100644
--- a/drivers/iio/frequency/Makefile
+++ b/drivers/iio/frequency/Makefile
@@ -5,6 +5,7 @@
# When adding new entries keep the list in alphabetical order
obj-$(CONFIG_AD9523) += ad9523.o
+obj-$(CONFIG_ADF41513) += adf41513.o
obj-$(CONFIG_ADF4350) += adf4350.o
obj-$(CONFIG_ADF4371) += adf4371.o
obj-$(CONFIG_ADF4377) += adf4377.o
diff --git a/drivers/iio/frequency/adf41513.c b/drivers/iio/frequency/adf41513.c
new file mode 100644
index 000000000000..5a0682667d1f
--- /dev/null
+++ b/drivers/iio/frequency/adf41513.c
@@ -0,0 +1,1111 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ADF41513 SPI PLL Frequency Synthesizer driver
+ *
+ * Copyright 2026 Analog Devices Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/log2.h>
+#include <linux/math64.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+/* Registers */
+#define ADF41513_REG0 0
+#define ADF41513_REG1 1
+#define ADF41513_REG2 2
+#define ADF41513_REG3 3
+#define ADF41513_REG4 4
+#define ADF41513_REG5 5
+#define ADF41513_REG6 6
+#define ADF41513_REG7 7
+#define ADF41513_REG8 8
+#define ADF41513_REG9 9
+#define ADF41513_REG10 10
+#define ADF41513_REG11 11
+#define ADF41513_REG12 12
+#define ADF41513_REG13 13
+#define ADF41513_REG_NUM 14
+
+#define ADF41513_SYNC_REG0 BIT(ADF41513_REG0)
+#define ADF41513_SYNC_REG1 BIT(ADF41513_REG1)
+#define ADF41513_SYNC_REG2 BIT(ADF41513_REG2)
+#define ADF41513_SYNC_REG3 BIT(ADF41513_REG3)
+#define ADF41513_SYNC_REG4 BIT(ADF41513_REG4)
+#define ADF41513_SYNC_REG5 BIT(ADF41513_REG5)
+#define ADF41513_SYNC_REG6 BIT(ADF41513_REG6)
+#define ADF41513_SYNC_REG7 BIT(ADF41513_REG7)
+#define ADF41513_SYNC_REG9 BIT(ADF41513_REG9)
+#define ADF41513_SYNC_REG11 BIT(ADF41513_REG11)
+#define ADF41513_SYNC_REG12 BIT(ADF41513_REG12)
+#define ADF41513_SYNC_REG13 BIT(ADF41513_REG13)
+#define ADF41513_SYNC_DIFF 0
+#define ADF41513_SYNC_ALL GENMASK(ADF41513_REG13, ADF41513_REG0)
+
+/* REG0 Bit Definitions */
+#define ADF41513_REG0_CTRL_BITS_MSK GENMASK(3, 0)
+#define ADF41513_REG0_INT_MSK GENMASK(19, 4)
+#define ADF41513_REG0_VAR_MOD_MSK BIT(28)
+
+/* REG1 Bit Definitions */
+#define ADF41513_REG1_FRAC1_MSK GENMASK(28, 4)
+#define ADF41513_REG1_DITHER2_MSK BIT(31)
+
+/* REG2 Bit Definitions */
+#define ADF41513_REG2_PHASE_VAL_MSK GENMASK(15, 4)
+#define ADF41513_REG2_PHASE_ADJ_MSK BIT(31)
+
+/* REG3 Bit Definitions */
+#define ADF41513_REG3_FRAC2_MSK GENMASK(27, 4)
+
+/* REG4 Bit Definitions */
+#define ADF41513_REG4_MOD2_MSK GENMASK(27, 4)
+
+/* REG5 Bit Definitions */
+#define ADF41513_REG5_CLK1_DIV_MSK GENMASK(15, 4)
+#define ADF41513_REG5_R_CNT_MSK GENMASK(20, 16)
+#define ADF41513_REG5_REF_DOUBLER_MSK BIT(21)
+#define ADF41513_REG5_RDIV2_MSK BIT(22)
+#define ADF41513_REG5_PRESCALER_MSK BIT(23)
+#define ADF41513_REG5_LSB_P1_MSK BIT(24)
+#define ADF41513_REG5_CP_CURRENT_MSK GENMASK(28, 25)
+#define ADF41513_REG5_DLD_MODES_MSK GENMASK(31, 30)
+
+/* REG6 Bit Definitions */
+#define ADF41513_REG6_COUNTER_RESET_MSK BIT(4)
+#define ADF41513_REG6_CP_TRISTATE_MSK BIT(5)
+#define ADF41513_REG6_POWER_DOWN_MSK BIT(6)
+#define ADF41513_REG6_PD_POLARITY_MSK BIT(7)
+#define ADF41513_REG6_LDP_MSK GENMASK(9, 8)
+#define ADF41513_REG6_CP_TRISTATE_PD_ON_MSK BIT(16)
+#define ADF41513_REG6_SD_RESET_MSK BIT(17)
+#define ADF41513_REG6_LOL_ENABLE_MSK BIT(18)
+#define ADF41513_REG6_ABP_MSK BIT(19)
+#define ADF41513_REG6_INT_MODE_MSK BIT(20)
+#define ADF41513_REG6_BLEED_ENABLE_MSK BIT(22)
+#define ADF41513_REG6_BLEED_POLARITY_MSK BIT(23)
+#define ADF41513_REG6_BLEED_CURRENT_MSK GENMASK(31, 24)
+
+/* REG7 Bit Definitions */
+#define ADF41513_REG7_CLK2_DIV_MSK GENMASK(17, 6)
+#define ADF41513_REG7_CLK_DIV_MODE_MSK GENMASK(19, 18)
+#define ADF41513_REG7_PS_BIAS_MSK GENMASK(21, 20)
+#define ADF41513_REG7_N_DELAY_MSK GENMASK(23, 22)
+#define ADF41513_REG7_LD_CLK_SEL_MSK BIT(26)
+#define ADF41513_REG7_LD_COUNT_MSK GENMASK(29, 27)
+
+/* REG9 Bit Definitions */
+#define ADF41513_REG9_LD_BIAS_MSK GENMASK(31, 30)
+
+/* REG11 Bit Definitions */
+#define ADF41513_REG11_POWER_DOWN_SEL_MSK BIT(31)
+
+/* REG12 Bit Definitions */
+#define ADF41513_REG12_READBACK_SEL_MSK GENMASK(19, 14)
+#define ADF41513_REG12_LE_SELECT_MSK BIT(20)
+#define ADF41513_REG12_MASTER_RESET_MSK BIT(22)
+#define ADF41513_REG12_LOGIC_LEVEL_MSK BIT(27)
+#define ADF41513_REG12_MUXOUT_MSK GENMASK(31, 28)
+
+/* MUXOUT Selection */
+#define ADF41513_MUXOUT_TRISTATE 0x0
+#define ADF41513_MUXOUT_DVDD 0x1
+#define ADF41513_MUXOUT_DGND 0x2
+#define ADF41513_MUXOUT_R_DIV 0x3
+#define ADF41513_MUXOUT_N_DIV 0x4
+#define ADF41513_MUXOUT_DIG_LD 0x6
+#define ADF41513_MUXOUT_SDO 0x7
+#define ADF41513_MUXOUT_READBACK 0x8
+#define ADF41513_MUXOUT_CLK1_DIV 0xA
+#define ADF41513_MUXOUT_R_DIV2 0xD
+#define ADF41513_MUXOUT_N_DIV2 0xE
+
+/* DLD Mode Selection */
+#define ADF41513_DLD_TRISTATE 0x0
+#define ADF41513_DLD_DIG_LD 0x1
+#define ADF41513_DLD_LOW 0x2
+#define ADF41513_DLD_HIGH 0x3
+
+/* Prescaler Selection */
+#define ADF41513_PRESCALER_4_5 0
+#define ADF41513_PRESCALER_8_9 1
+#define ADF41513_PRESCALER_AUTO 2
+
+/* Specifications */
+#define ADF41513_HZ_PER_GHZ 1000000000UL
+#define ADF41510_MAX_RF_FREQ_HZ (10ULL * ADF41513_HZ_PER_GHZ)
+#define ADF41513_MIN_RF_FREQ_HZ (1ULL * ADF41513_HZ_PER_GHZ)
+#define ADF41513_MAX_RF_FREQ_HZ (26500ULL * HZ_PER_MHZ)
+
+#define ADF41513_MIN_REF_FREQ_HZ (10 * HZ_PER_MHZ)
+#define ADF41513_MAX_REF_FREQ_HZ (800 * HZ_PER_MHZ)
+#define ADF41513_MAX_REF_FREQ_DOUBLER_HZ (225 * HZ_PER_MHZ)
+
+#define ADF41513_MAX_PFD_FREQ_INT_N_UHZ (250ULL * MEGA * MICROHZ_PER_HZ)
+#define ADF41513_MAX_PFD_FREQ_FRAC_N_UHZ (125ULL * MEGA * MICROHZ_PER_HZ)
+#define ADF41513_MAX_FREQ_RESOLUTION_UHZ (100ULL * KILO * MICROHZ_PER_HZ)
+
+#define ADF41513_MIN_INT_4_5 20
+#define ADF41513_MAX_INT_4_5 511
+#define ADF41513_MIN_INT_8_9 64
+#define ADF41513_MAX_INT_8_9 1023
+
+#define ADF41513_MIN_INT_FRAC_4_5 23
+#define ADF41513_MIN_INT_FRAC_8_9 75
+
+#define ADF41513_MIN_R_CNT 1
+#define ADF41513_MAX_R_CNT 32
+
+#define ADF41513_MIN_R_SET 1800
+#define ADF41513_DEFAULT_R_SET 2700
+#define ADF41513_MAX_R_SET 10000
+
+#define ADF41513_MIN_CP_VOLTAGE_mV 810
+#define ADF41513_DEFAULT_CP_VOLTAGE_mV 6480
+#define ADF41513_MAX_CP_VOLTAGE_mV 12960
+
+#define ADF41513_LD_COUNT_FAST_MIN 2
+#define ADF41513_LD_COUNT_FAST_LIMIT 64
+#define ADF41513_LD_COUNT_MIN 64
+#define ADF41513_LD_COUNT_MAX 8192
+
+#define ADF41513_FIXED_MODULUS BIT(25)
+#define ADF41513_MAX_MOD2 (BIT(24) - 1)
+#define ADF41513_MAX_PHASE_VAL (BIT(12) - 1)
+#define ADF41513_MAX_CLK_DIVIDER (BIT(12) - 1)
+
+#define ADF41513_HZ_DECIMAL_PRECISION 6
+#define ADF41513_PS_BIAS_INIT 0x2
+#define ADF41513_MAX_PHASE_MICRORAD ((2 * 314159265UL) / 100)
+
+enum {
+ ADF41513_FREQ,
+ ADF41513_POWER_DOWN,
+ ADF41513_FREQ_RESOLUTION,
+};
+
+enum adf41513_pll_mode {
+ ADF41513_MODE_INVALID,
+ ADF41513_MODE_INTEGER_N,
+ ADF41513_MODE_FIXED_MODULUS,
+ ADF41513_MODE_VARIABLE_MODULUS,
+};
+
+struct adf41513_chip_info {
+ const char *name;
+ u64 max_rf_freq_hz;
+ bool has_prescaler_8_9;
+};
+
+struct adf41513_data {
+ u64 power_up_frequency_hz;
+ u64 freq_resolution_uhz;
+ u32 charge_pump_voltage_mv;
+ u32 lock_detect_count;
+
+ u8 ref_div_factor;
+ bool ref_doubler_en;
+ bool ref_div2_en;
+ bool phase_detector_polarity;
+
+ bool logic_lvl_1v8_en;
+};
+
+struct adf41513_pll_settings {
+ enum adf41513_pll_mode mode;
+
+ /* reference path parameters */
+ u8 r_counter;
+ u8 ref_doubler;
+ u8 ref_div2;
+ u8 prescaler;
+
+ /* frequency parameters */
+ u64 target_frequency_uhz;
+ u64 actual_frequency_uhz;
+ u64 pfd_frequency_uhz;
+
+ /* pll parameters */
+ u32 frac1;
+ u32 frac2;
+ u32 mod2;
+ u16 int_value;
+};
+
+struct adf41513_state {
+ const struct adf41513_chip_info *chip_info;
+ struct spi_device *spi;
+ struct gpio_desc *lock_detect;
+ struct gpio_desc *chip_enable;
+ struct clk *ref_clk;
+ u32 ref_freq_hz;
+
+ /*
+ * Lock for accessing device registers. Some operations require
+ * multiple consecutive R/W operations, during which the device
+ * shouldn't be interrupted. The buffers are also shared across
+ * all operations so need to be protected on stand alone reads and
+ * writes.
+ */
+ struct mutex lock;
+
+ /* Cached register values */
+ u32 regs[ADF41513_REG_NUM];
+ u32 regs_hw[ADF41513_REG_NUM];
+
+ struct adf41513_data data;
+ struct adf41513_pll_settings settings;
+};
+
+static const char * const adf41513_power_supplies[] = {
+ "avdd1", "avdd2", "avdd3", "avdd4", "avdd5", "vp",
+};
+
+static int adf41513_sync_config(struct adf41513_state *st, u16 sync_mask)
+{
+ __be32 d32;
+ int ret, i;
+
+ /* write registers in reverse order (R13 to R0)*/
+ for (i = ADF41513_REG13; i >= ADF41513_REG0; i--) {
+ if (st->regs_hw[i] == st->regs[i] && !(sync_mask & BIT(i)))
+ continue;
+
+ d32 = cpu_to_be32(st->regs[i] | i);
+ ret = spi_write_then_read(st->spi, &d32, sizeof(d32), NULL, 0);
+ if (ret < 0)
+ return ret;
+ st->regs_hw[i] = st->regs[i];
+ dev_dbg(&st->spi->dev, "REG%d <= 0x%08X\n", i, st->regs[i] | i);
+ }
+
+ return 0;
+}
+
+static u64 adf41513_pll_get_rate(struct adf41513_state *st)
+{
+ struct adf41513_pll_settings *cfg = &st->settings;
+
+ if (cfg->mode != ADF41513_MODE_INVALID)
+ return cfg->actual_frequency_uhz;
+
+ /* get pll settings from regs_hw */
+ cfg->int_value = FIELD_GET(ADF41513_REG0_INT_MSK, st->regs_hw[ADF41513_REG0]);
+ cfg->frac1 = FIELD_GET(ADF41513_REG1_FRAC1_MSK, st->regs_hw[ADF41513_REG1]);
+ cfg->frac2 = FIELD_GET(ADF41513_REG3_FRAC2_MSK, st->regs_hw[ADF41513_REG3]);
+ cfg->mod2 = FIELD_GET(ADF41513_REG4_MOD2_MSK, st->regs_hw[ADF41513_REG4]);
+ cfg->r_counter = FIELD_GET(ADF41513_REG5_R_CNT_MSK, st->regs_hw[ADF41513_REG5]);
+ cfg->ref_doubler = FIELD_GET(ADF41513_REG5_REF_DOUBLER_MSK, st->regs_hw[ADF41513_REG5]);
+ cfg->ref_div2 = FIELD_GET(ADF41513_REG5_RDIV2_MSK, st->regs_hw[ADF41513_REG5]);
+ cfg->prescaler = FIELD_GET(ADF41513_REG5_PRESCALER_MSK, st->regs_hw[ADF41513_REG5]);
+
+ /* calculate pfd frequency */
+ cfg->pfd_frequency_uhz = (u64)st->ref_freq_hz * MICRO;
+ if (cfg->ref_doubler)
+ cfg->pfd_frequency_uhz <<= 1;
+ if (cfg->ref_div2)
+ cfg->pfd_frequency_uhz >>= 1;
+ cfg->pfd_frequency_uhz = div_u64(cfg->pfd_frequency_uhz, cfg->r_counter);
+ cfg->actual_frequency_uhz = (u64)cfg->int_value * cfg->pfd_frequency_uhz;
+
+ /* check if int mode is selected */
+ if (FIELD_GET(ADF41513_REG6_INT_MODE_MSK, st->regs_hw[ADF41513_REG6])) {
+ cfg->mode = ADF41513_MODE_INTEGER_N;
+ } else {
+ cfg->actual_frequency_uhz += mul_u64_u32_div(cfg->pfd_frequency_uhz,
+ cfg->frac1,
+ ADF41513_FIXED_MODULUS);
+
+ /* check if variable modulus is selected */
+ if (FIELD_GET(ADF41513_REG0_VAR_MOD_MSK, st->regs_hw[ADF41513_REG0])) {
+ cfg->actual_frequency_uhz +=
+ mul_u64_u64_div_u64(cfg->frac2,
+ cfg->pfd_frequency_uhz,
+ (u64)cfg->mod2 * ADF41513_FIXED_MODULUS);
+
+ cfg->mode = ADF41513_MODE_VARIABLE_MODULUS;
+ } else {
+ /* LSB_P1 offset */
+ if (!FIELD_GET(ADF41513_REG5_LSB_P1_MSK, st->regs_hw[ADF41513_REG5]))
+ cfg->actual_frequency_uhz +=
+ div_u64(cfg->pfd_frequency_uhz,
+ 2 * ADF41513_FIXED_MODULUS);
+ cfg->mode = ADF41513_MODE_FIXED_MODULUS;
+ }
+ }
+
+ cfg->target_frequency_uhz = cfg->actual_frequency_uhz;
+
+ return cfg->actual_frequency_uhz;
+}
+
+static int adf41513_calc_pfd_frequency(struct adf41513_state *st,
+ struct adf41513_pll_settings *result,
+ u64 fpfd_limit_uhz)
+{
+ result->ref_div2 = st->data.ref_div2_en;
+ result->ref_doubler = st->data.ref_doubler_en;
+
+ if (st->data.ref_doubler_en &&
+ st->ref_freq_hz > ADF41513_MAX_REF_FREQ_DOUBLER_HZ) {
+ result->ref_doubler = 0;
+ dev_warn(&st->spi->dev, "Disabling ref doubler due to high reference frequency\n");
+ }
+
+ result->r_counter = st->data.ref_div_factor - 1;
+ do {
+ result->r_counter++;
+ /* f_PFD = REF_IN × ((1 + D)/(R × (1 + T))) */
+ result->pfd_frequency_uhz = (u64)st->ref_freq_hz * MICRO;
+ if (result->ref_doubler)
+ result->pfd_frequency_uhz <<= 1;
+ if (result->ref_div2)
+ result->pfd_frequency_uhz >>= 1;
+ result->pfd_frequency_uhz = div_u64(result->pfd_frequency_uhz,
+ result->r_counter);
+ } while (result->pfd_frequency_uhz > fpfd_limit_uhz);
+
+ if (result->r_counter > ADF41513_MAX_R_CNT) {
+ dev_err(&st->spi->dev, "Cannot optimize PFD frequency\n");
+ return -ERANGE;
+ }
+
+ return 0;
+}
+
+static int adf41513_calc_integer_n(struct adf41513_state *st,
+ struct adf41513_pll_settings *result)
+{
+ u16 max_int = st->chip_info->has_prescaler_8_9 ?
+ ADF41513_MAX_INT_8_9 : ADF41513_MAX_INT_4_5;
+ u64 freq_error_uhz;
+ u16 int_value = div64_u64_rem(result->target_frequency_uhz, result->pfd_frequency_uhz,
+ &freq_error_uhz);
+
+ /* check if freq error is within a tolerance of 1/2 resolution */
+ if (freq_error_uhz > (result->pfd_frequency_uhz >> 1) && int_value < max_int) {
+ int_value++;
+ freq_error_uhz = result->pfd_frequency_uhz - freq_error_uhz;
+ }
+
+ if (freq_error_uhz > st->data.freq_resolution_uhz)
+ return -ERANGE;
+
+ /* set prescaler */
+ if (st->chip_info->has_prescaler_8_9 && int_value >= ADF41513_MIN_INT_8_9 &&
+ int_value <= ADF41513_MAX_INT_8_9)
+ result->prescaler = 1;
+ else if (int_value >= ADF41513_MIN_INT_4_5 && int_value <= ADF41513_MAX_INT_4_5)
+ result->prescaler = 0;
+ else
+ return -ERANGE;
+
+ result->actual_frequency_uhz = (u64)int_value * result->pfd_frequency_uhz;
+ result->mode = ADF41513_MODE_INTEGER_N;
+ result->int_value = int_value;
+ result->frac1 = 0;
+ result->frac2 = 0;
+ result->mod2 = 0;
+
+ return 0;
+}
+
+static int adf41513_calc_fixed_mod(struct adf41513_state *st,
+ struct adf41513_pll_settings *result)
+{
+ u64 freq_error_uhz;
+ u64 resolution_uhz = div_u64(result->pfd_frequency_uhz, ADF41513_FIXED_MODULUS);
+ u64 target_frequency_uhz = result->target_frequency_uhz;
+ u32 frac1;
+ u16 int_value;
+ bool lsb_p1_offset = !FIELD_GET(ADF41513_REG5_LSB_P1_MSK, st->regs_hw[ADF41513_REG5]);
+
+ /* LSB_P1 adds a frequency offset of f_pfd/2^26 */
+ if (lsb_p1_offset)
+ target_frequency_uhz -= resolution_uhz >> 1;
+
+ int_value = div64_u64_rem(target_frequency_uhz, result->pfd_frequency_uhz,
+ &freq_error_uhz);
+
+ if (st->chip_info->has_prescaler_8_9 && int_value >= ADF41513_MIN_INT_FRAC_8_9 &&
+ int_value <= ADF41513_MAX_INT_8_9)
+ result->prescaler = 1;
+ else if (int_value >= ADF41513_MIN_INT_FRAC_4_5 && int_value <= ADF41513_MAX_INT_4_5)
+ result->prescaler = 0;
+ else
+ return -ERANGE;
+
+ /* compute frac1 and fixed modulus error */
+ frac1 = mul_u64_u64_div_u64(freq_error_uhz, ADF41513_FIXED_MODULUS,
+ result->pfd_frequency_uhz);
+ freq_error_uhz -= mul_u64_u32_div(result->pfd_frequency_uhz, frac1,
+ ADF41513_FIXED_MODULUS);
+
+ /* check if freq error is within a tolerance of 1/2 resolution */
+ if (freq_error_uhz > (resolution_uhz >> 1) && frac1 < (ADF41513_FIXED_MODULUS - 1)) {
+ frac1++;
+ freq_error_uhz = resolution_uhz - freq_error_uhz;
+ }
+
+ if (freq_error_uhz > st->data.freq_resolution_uhz)
+ return -ERANGE;
+
+ /* integer part */
+ result->actual_frequency_uhz = (u64)int_value * result->pfd_frequency_uhz;
+ /* fractional part */
+ if (lsb_p1_offset)
+ result->actual_frequency_uhz += (resolution_uhz >> 1);
+ result->actual_frequency_uhz += mul_u64_u32_div(result->pfd_frequency_uhz, frac1,
+ ADF41513_FIXED_MODULUS);
+ result->mode = ADF41513_MODE_FIXED_MODULUS;
+ result->int_value = int_value;
+ result->frac1 = frac1;
+ result->frac2 = 0;
+ result->mod2 = 0;
+
+ return 0;
+}
+
+static int adf41513_calc_variable_mod(struct adf41513_state *st,
+ struct adf41513_pll_settings *result)
+{
+ u64 freq_error_uhz, mod2;
+ u32 frac1, frac2;
+ u16 int_value = div64_u64_rem(result->target_frequency_uhz,
+ result->pfd_frequency_uhz,
+ &freq_error_uhz);
+
+ if (st->chip_info->has_prescaler_8_9 && int_value >= ADF41513_MIN_INT_FRAC_8_9 &&
+ int_value <= ADF41513_MAX_INT_8_9)
+ result->prescaler = 1;
+ else if (int_value >= ADF41513_MIN_INT_FRAC_4_5 && int_value <= ADF41513_MAX_INT_4_5)
+ result->prescaler = 0;
+ else
+ return -ERANGE;
+
+ /* calculate required mod2 based on target resolution / 2 */
+ mod2 = DIV64_U64_ROUND_CLOSEST(result->pfd_frequency_uhz << 1,
+ st->data.freq_resolution_uhz * ADF41513_FIXED_MODULUS);
+ /* ensure mod2 is at least 2 for meaningful operation */
+ mod2 = clamp(mod2, 2, ADF41513_MAX_MOD2);
+
+ /* calculate frac1 and frac2 */
+ frac1 = mul_u64_u64_div_u64(freq_error_uhz, ADF41513_FIXED_MODULUS,
+ result->pfd_frequency_uhz);
+ freq_error_uhz -= mul_u64_u32_div(result->pfd_frequency_uhz, frac1,
+ ADF41513_FIXED_MODULUS);
+ frac2 = mul_u64_u64_div_u64(freq_error_uhz, mod2 * ADF41513_FIXED_MODULUS,
+ result->pfd_frequency_uhz);
+
+ /* integer part */
+ result->actual_frequency_uhz = (u64)int_value * result->pfd_frequency_uhz;
+ /* fractional part */
+ result->actual_frequency_uhz += mul_u64_u64_div_u64(mod2 * frac1 + frac2,
+ result->pfd_frequency_uhz,
+ mod2 * ADF41513_FIXED_MODULUS);
+ result->mode = ADF41513_MODE_VARIABLE_MODULUS;
+ result->int_value = int_value;
+ result->frac1 = frac1;
+ result->frac2 = frac2;
+ result->mod2 = mod2;
+
+ return 0;
+}
+
+static int adf41513_calc_pll_settings(struct adf41513_state *st,
+ struct adf41513_pll_settings *result,
+ u64 rf_out_uhz)
+{
+ u64 max_rf_freq_uhz = st->chip_info->max_rf_freq_hz * MICRO;
+ u64 min_rf_freq_uhz = ADF41513_MIN_RF_FREQ_HZ * MICRO;
+ u64 pfd_freq_limit_uhz;
+ int ret;
+
+ if (rf_out_uhz < min_rf_freq_uhz || rf_out_uhz > max_rf_freq_uhz) {
+ dev_err(&st->spi->dev, "RF frequency %llu uHz out of range [%llu, %llu] uHz\n",
+ rf_out_uhz, min_rf_freq_uhz, max_rf_freq_uhz);
+ return -EINVAL;
+ }
+
+ result->target_frequency_uhz = rf_out_uhz;
+
+ /* try integer-N first (best phase noise performance) */
+ pfd_freq_limit_uhz = min(div_u64(rf_out_uhz, ADF41513_MIN_INT_4_5),
+ ADF41513_MAX_PFD_FREQ_INT_N_UHZ);
+ ret = adf41513_calc_pfd_frequency(st, result, pfd_freq_limit_uhz);
+ if (ret < 0)
+ return ret;
+
+ if (adf41513_calc_integer_n(st, result) == 0)
+ return 0;
+
+ /* try fractional-N: recompute pfd frequency if necessary */
+ pfd_freq_limit_uhz = min(div_u64(rf_out_uhz, ADF41513_MIN_INT_FRAC_4_5),
+ ADF41513_MAX_PFD_FREQ_FRAC_N_UHZ);
+ if (pfd_freq_limit_uhz < result->pfd_frequency_uhz) {
+ ret = adf41513_calc_pfd_frequency(st, result, pfd_freq_limit_uhz);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* fixed-modulus attempt */
+ if (adf41513_calc_fixed_mod(st, result) == 0)
+ return 0;
+
+ /* variable-modulus attempt */
+ ret = adf41513_calc_variable_mod(st, result);
+ if (ret < 0) {
+ dev_err(&st->spi->dev,
+ "no valid PLL configuration found for %llu uHz\n",
+ rf_out_uhz);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int adf41513_set_frequency(struct adf41513_state *st, u64 freq_uhz, u16 sync_mask)
+{
+ struct adf41513_pll_settings result;
+ int ret;
+
+ ret = adf41513_calc_pll_settings(st, &result, freq_uhz);
+ if (ret < 0)
+ return ret;
+
+ /* apply computed results to pll settings */
+ st->settings = result;
+
+ dev_dbg(&st->spi->dev,
+ "%s mode: int=%u, frac1=%u, frac2=%u, mod2=%u, fpdf=%llu Hz, prescaler=%s\n",
+ (result.mode == ADF41513_MODE_INTEGER_N) ? "integer-n" :
+ (result.mode == ADF41513_MODE_FIXED_MODULUS) ? "fixed-modulus" : "variable-modulus",
+ result.int_value, result.frac1, result.frac2, result.mod2,
+ div64_u64(result.pfd_frequency_uhz, MICRO),
+ result.prescaler ? "8/9" : "4/5");
+
+ st->regs[ADF41513_REG0] = FIELD_PREP(ADF41513_REG0_INT_MSK,
+ st->settings.int_value);
+ if (st->settings.mode == ADF41513_MODE_VARIABLE_MODULUS)
+ st->regs[ADF41513_REG0] |= ADF41513_REG0_VAR_MOD_MSK;
+
+ st->regs[ADF41513_REG1] = FIELD_PREP(ADF41513_REG1_FRAC1_MSK,
+ st->settings.frac1);
+ if (st->settings.mode != ADF41513_MODE_INTEGER_N)
+ st->regs[ADF41513_REG1] |= ADF41513_REG1_DITHER2_MSK;
+
+ st->regs[ADF41513_REG3] = FIELD_PREP(ADF41513_REG3_FRAC2_MSK,
+ st->settings.frac2);
+ FIELD_MODIFY(ADF41513_REG4_MOD2_MSK, &st->regs[ADF41513_REG4],
+ st->settings.mod2);
+ FIELD_MODIFY(ADF41513_REG5_R_CNT_MSK, &st->regs[ADF41513_REG5],
+ st->settings.r_counter);
+ FIELD_MODIFY(ADF41513_REG5_REF_DOUBLER_MSK, &st->regs[ADF41513_REG5],
+ st->settings.ref_doubler);
+ FIELD_MODIFY(ADF41513_REG5_RDIV2_MSK, &st->regs[ADF41513_REG5],
+ st->settings.ref_div2);
+ FIELD_MODIFY(ADF41513_REG5_PRESCALER_MSK, &st->regs[ADF41513_REG5],
+ st->settings.prescaler);
+
+ if (st->settings.mode == ADF41513_MODE_INTEGER_N) {
+ st->regs[ADF41513_REG6] |= ADF41513_REG6_INT_MODE_MSK;
+ st->regs[ADF41513_REG6] &= ~ADF41513_REG6_BLEED_ENABLE_MSK;
+ } else {
+ st->regs[ADF41513_REG6] &= ~ADF41513_REG6_INT_MODE_MSK;
+ st->regs[ADF41513_REG6] |= ADF41513_REG6_BLEED_ENABLE_MSK;
+ }
+
+ return adf41513_sync_config(st, sync_mask | ADF41513_SYNC_REG0);
+}
+
+static int adf41513_suspend(struct adf41513_state *st)
+{
+ st->regs[ADF41513_REG6] |= FIELD_PREP(ADF41513_REG6_POWER_DOWN_MSK, 1);
+ return adf41513_sync_config(st, ADF41513_SYNC_DIFF);
+}
+
+static int adf41513_resume(struct adf41513_state *st)
+{
+ st->regs[ADF41513_REG6] &= ~ADF41513_REG6_POWER_DOWN_MSK;
+ return adf41513_sync_config(st, ADF41513_SYNC_DIFF);
+}
+
+static ssize_t adf41513_read_uhz(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct adf41513_state *st = iio_priv(indio_dev);
+ u64 freq_uhz, int_hz;
+ u32 frac_uhz;
+
+ guard(mutex)(&st->lock);
+
+ switch (private) {
+ case ADF41513_FREQ:
+ freq_uhz = adf41513_pll_get_rate(st);
+ if (st->lock_detect &&
+ !gpiod_get_value_cansleep(st->lock_detect)) {
+ dev_dbg(&st->spi->dev, "PLL un-locked\n");
+ return -EBUSY;
+ }
+ break;
+ case ADF41513_FREQ_RESOLUTION:
+ freq_uhz = st->data.freq_resolution_uhz;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ int_hz = div_u64_rem(freq_uhz, MICRO, &frac_uhz);
+ return sysfs_emit(buf, "%llu.%06u\n", int_hz, frac_uhz);
+}
+
+static ssize_t adf41513_read_powerdown(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct adf41513_state *st = iio_priv(indio_dev);
+ u32 val;
+
+ guard(mutex)(&st->lock);
+
+ switch (private) {
+ case ADF41513_POWER_DOWN:
+ val = FIELD_GET(ADF41513_REG6_POWER_DOWN_MSK,
+ st->regs_hw[ADF41513_REG6]);
+ return sysfs_emit(buf, "%u\n", val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static ssize_t adf41513_write_uhz(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len)
+{
+ struct adf41513_state *st = iio_priv(indio_dev);
+ s64 int_hz, frac_uhz;
+ u64 freq_uhz;
+ int ret;
+
+ ret = iio_str_to_fixpoint64(buf, MICRO / 10, &int_hz, &frac_uhz);
+ if (ret)
+ return ret;
+
+ freq_uhz = int_hz * MICRO + frac_uhz;
+ guard(mutex)(&st->lock);
+
+ switch ((u32)private) {
+ case ADF41513_FREQ:
+ ret = adf41513_set_frequency(st, freq_uhz, ADF41513_SYNC_DIFF);
+ break;
+ case ADF41513_FREQ_RESOLUTION:
+ if (freq_uhz == 0 || freq_uhz > ADF41513_MAX_FREQ_RESOLUTION_UHZ)
+ return -EINVAL;
+ st->data.freq_resolution_uhz = freq_uhz;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret ?: len;
+}
+
+static ssize_t adf41513_write_powerdown(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len)
+{
+ struct adf41513_state *st = iio_priv(indio_dev);
+ unsigned long readin;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &readin);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&st->lock);
+
+ switch ((u32)private) {
+ case ADF41513_POWER_DOWN:
+ if (readin)
+ ret = adf41513_suspend(st);
+ else
+ ret = adf41513_resume(st);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret ?: len;
+}
+
+#define _ADF41513_EXT_PD_INFO(_name, _ident) { \
+ .name = _name, \
+ .read = adf41513_read_powerdown, \
+ .write = adf41513_write_powerdown, \
+ .private = _ident, \
+ .shared = IIO_SEPARATE, \
+}
+
+#define _ADF41513_EXT_UHZ_INFO(_name, _ident) { \
+ .name = _name, \
+ .read = adf41513_read_uhz, \
+ .write = adf41513_write_uhz, \
+ .private = _ident, \
+ .shared = IIO_SEPARATE, \
+}
+
+static const struct iio_chan_spec_ext_info adf41513_ext_info[] = {
+ /*
+ * Ideally we would use IIO_CHAN_INFO_FREQUENCY, but the device supports
+ * frequency values greater 2^32 with sub-Hz resolution, i.e. 64-bit
+ * fixed point with 6 decimal places values are used to represent
+ * frequencies.
+ */
+ _ADF41513_EXT_UHZ_INFO("frequency", ADF41513_FREQ),
+ _ADF41513_EXT_UHZ_INFO("frequency_resolution", ADF41513_FREQ_RESOLUTION),
+ _ADF41513_EXT_PD_INFO("powerdown", ADF41513_POWER_DOWN),
+ { }
+};
+
+static const struct iio_chan_spec adf41513_chan = {
+ .type = IIO_ALTVOLTAGE,
+ .indexed = 1,
+ .output = 1,
+ .channel = 0,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PHASE),
+ .ext_info = adf41513_ext_info,
+};
+
+static int adf41513_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long info)
+{
+ struct adf41513_state *st = iio_priv(indio_dev);
+ u64 phase_urad;
+ u16 phase_val;
+
+ guard(mutex)(&st->lock);
+
+ switch (info) {
+ case IIO_CHAN_INFO_PHASE:
+ phase_val = FIELD_GET(ADF41513_REG2_PHASE_VAL_MSK,
+ st->regs_hw[ADF41513_REG2]);
+ phase_urad = (u64)phase_val * ADF41513_MAX_PHASE_MICRORAD;
+ phase_urad >>= 12;
+ /*
+ * Before the 12-bit rshift phase_urad can be represented with
+ * 35 bits at most. After the shift it will fit in 32-bit value.
+ */
+ *val = (u32)phase_urad / MICRO;
+ *val2 = (u32)phase_urad % MICRO;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adf41513_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long info)
+{
+ struct adf41513_state *st = iio_priv(indio_dev);
+ u64 phase_urad;
+ u16 phase_val;
+
+ guard(mutex)(&st->lock);
+
+ switch (info) {
+ case IIO_CHAN_INFO_PHASE:
+ if (val < 0 || val2 < 0)
+ return -EINVAL;
+
+ phase_urad = (u64)val * MICRO + val2;
+ if (phase_urad >= ADF41513_MAX_PHASE_MICRORAD)
+ return -EINVAL;
+
+ phase_val = DIV_U64_ROUND_CLOSEST(phase_urad << 12,
+ ADF41513_MAX_PHASE_MICRORAD);
+ phase_val = min(phase_val, ADF41513_MAX_PHASE_VAL);
+ st->regs[ADF41513_REG2] |= ADF41513_REG2_PHASE_ADJ_MSK;
+ FIELD_MODIFY(ADF41513_REG2_PHASE_VAL_MSK,
+ &st->regs[ADF41513_REG2], phase_val);
+ return adf41513_sync_config(st, ADF41513_SYNC_REG0);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adf41513_reg_access(struct iio_dev *indio_dev, unsigned int reg,
+ unsigned int writeval, unsigned int *readval)
+{
+ struct adf41513_state *st = iio_priv(indio_dev);
+
+ if (reg > ADF41513_REG13)
+ return -EINVAL;
+
+ guard(mutex)(&st->lock);
+
+ if (!readval) {
+ if (reg <= ADF41513_REG6)
+ st->settings.mode = ADF41513_MODE_INVALID;
+ st->regs[reg] = writeval & ~0xF; /* Clear control bits */
+ return adf41513_sync_config(st, BIT(reg));
+ }
+
+ *readval = st->regs_hw[reg];
+ return 0;
+}
+
+static const struct iio_info adf41513_info = {
+ .read_raw = adf41513_read_raw,
+ .write_raw = adf41513_write_raw,
+ .debugfs_reg_access = &adf41513_reg_access,
+};
+
+static int adf41513_parse_fw(struct adf41513_state *st)
+{
+ struct device *dev = &st->spi->dev;
+ u32 tmp, cp_resistance, cp_current;
+ int ret;
+
+ tmp = ADF41510_MAX_RF_FREQ_HZ / MEGA;
+ device_property_read_u32(dev, "adi,power-up-frequency-mhz", &tmp);
+ st->data.power_up_frequency_hz = (u64)tmp * MEGA;
+ if (st->data.power_up_frequency_hz < ADF41513_MIN_RF_FREQ_HZ ||
+ st->data.power_up_frequency_hz > ADF41513_MAX_RF_FREQ_HZ)
+ return dev_err_probe(dev, -ERANGE,
+ "power-up frequency %llu Hz out of range\n",
+ st->data.power_up_frequency_hz);
+
+ tmp = ADF41513_MIN_R_CNT;
+ device_property_read_u32(dev, "adi,reference-div-factor", &tmp);
+ if (tmp < ADF41513_MIN_R_CNT || tmp > ADF41513_MAX_R_CNT)
+ return dev_err_probe(dev, -ERANGE,
+ "invalid reference div factor %u\n", tmp);
+ st->data.ref_div_factor = tmp;
+
+ st->data.ref_doubler_en = device_property_read_bool(dev, "adi,reference-doubler-enable");
+ st->data.ref_div2_en = device_property_read_bool(dev, "adi,reference-div2-enable");
+
+ cp_resistance = ADF41513_DEFAULT_R_SET;
+ device_property_read_u32(dev, "adi,charge-pump-resistor-ohms", &cp_resistance);
+ if (cp_resistance < ADF41513_MIN_R_SET || cp_resistance > ADF41513_MAX_R_SET)
+ return dev_err_probe(dev, -ERANGE, "R_SET %u Ohms out of range\n", cp_resistance);
+
+ st->data.charge_pump_voltage_mv = ADF41513_DEFAULT_CP_VOLTAGE_mV;
+ ret = device_property_read_u32(dev, "adi,charge-pump-current-microamp", &cp_current);
+ if (!ret) {
+ tmp = DIV_ROUND_CLOSEST(cp_current * cp_resistance, MILLI); /* convert to mV */
+ if (tmp < ADF41513_MIN_CP_VOLTAGE_mV || tmp > ADF41513_MAX_CP_VOLTAGE_mV)
+ return dev_err_probe(dev, -ERANGE, "I_CP %u uA (%u Ohms) out of range\n",
+ cp_current, cp_resistance);
+ st->data.charge_pump_voltage_mv = tmp;
+ }
+
+ st->data.phase_detector_polarity =
+ device_property_read_bool(dev, "adi,phase-detector-polarity-positive-enable");
+
+ st->data.logic_lvl_1v8_en = device_property_read_bool(dev, "adi,logic-level-1v8-enable");
+
+ tmp = ADF41513_LD_COUNT_MIN;
+ device_property_read_u32(dev, "adi,lock-detector-count", &tmp);
+ if (tmp < ADF41513_LD_COUNT_FAST_MIN || tmp > ADF41513_LD_COUNT_MAX ||
+ !is_power_of_2(tmp))
+ return dev_err_probe(dev, -ERANGE,
+ "invalid lock detect count: %u\n", tmp);
+ st->data.lock_detect_count = tmp;
+
+ st->data.freq_resolution_uhz = MICROHZ_PER_HZ;
+
+ return 0;
+}
+
+static int adf41513_setup(struct adf41513_state *st)
+{
+ u32 tmp;
+
+ memset(st->regs_hw, 0xFF, sizeof(st->regs_hw));
+
+ /* assuming DLD pin is used for lock detection */
+ st->regs[ADF41513_REG5] = FIELD_PREP(ADF41513_REG5_DLD_MODES_MSK,
+ ADF41513_DLD_DIG_LD);
+
+ tmp = DIV_ROUND_CLOSEST(st->data.charge_pump_voltage_mv, ADF41513_MIN_CP_VOLTAGE_mV);
+ st->regs[ADF41513_REG5] |= FIELD_PREP(ADF41513_REG5_CP_CURRENT_MSK, tmp - 1);
+
+ st->regs[ADF41513_REG6] = ADF41513_REG6_ABP_MSK |
+ ADF41513_REG6_LOL_ENABLE_MSK |
+ ADF41513_REG6_SD_RESET_MSK;
+ if (st->data.phase_detector_polarity)
+ st->regs[ADF41513_REG6] |= ADF41513_REG6_PD_POLARITY_MSK;
+
+ st->regs[ADF41513_REG7] = FIELD_PREP(ADF41513_REG7_PS_BIAS_MSK,
+ ADF41513_PS_BIAS_INIT);
+ tmp = ilog2(st->data.lock_detect_count);
+ if (st->data.lock_detect_count < ADF41513_LD_COUNT_FAST_LIMIT) {
+ tmp -= const_ilog2(ADF41513_LD_COUNT_FAST_MIN);
+ st->regs[ADF41513_REG7] |= ADF41513_REG7_LD_CLK_SEL_MSK;
+ } else {
+ tmp -= const_ilog2(ADF41513_LD_COUNT_MIN);
+ }
+ st->regs[ADF41513_REG7] |= FIELD_PREP(ADF41513_REG7_LD_COUNT_MSK, tmp);
+
+ st->regs[ADF41513_REG11] = ADF41513_REG11_POWER_DOWN_SEL_MSK;
+ st->regs[ADF41513_REG12] = FIELD_PREP(ADF41513_REG12_LOGIC_LEVEL_MSK,
+ st->data.logic_lvl_1v8_en ? 0 : 1);
+
+ /* perform initialization sequence with power-up frequency */
+ return adf41513_set_frequency(st, st->data.power_up_frequency_hz * MICRO,
+ ADF41513_SYNC_ALL);
+}
+
+static void adf41513_power_down(void *data)
+{
+ struct adf41513_state *st = data;
+
+ adf41513_suspend(st);
+ gpiod_set_value_cansleep(st->chip_enable, 0);
+}
+
+static int adf41513_pm_suspend(struct device *dev)
+{
+ return adf41513_suspend(dev_get_drvdata(dev));
+}
+
+static int adf41513_pm_resume(struct device *dev)
+{
+ return adf41513_resume(dev_get_drvdata(dev));
+}
+
+static const struct adf41513_chip_info adf41510_chip_info = {
+ .name = "adf41510",
+ .max_rf_freq_hz = ADF41510_MAX_RF_FREQ_HZ,
+ .has_prescaler_8_9 = false,
+};
+
+static const struct adf41513_chip_info adf41513_chip_info = {
+ .name = "adf41513",
+ .max_rf_freq_hz = ADF41513_MAX_RF_FREQ_HZ,
+ .has_prescaler_8_9 = true,
+};
+
+static int adf41513_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct iio_dev *indio_dev;
+ struct adf41513_state *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ st->spi = spi;
+ st->chip_info = spi_get_device_match_data(spi);
+ if (!st->chip_info)
+ return -EINVAL;
+
+ spi_set_drvdata(spi, st);
+
+ st->ref_clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(st->ref_clk))
+ return PTR_ERR(st->ref_clk);
+
+ st->ref_freq_hz = clk_get_rate(st->ref_clk);
+ if (st->ref_freq_hz < ADF41513_MIN_REF_FREQ_HZ ||
+ st->ref_freq_hz > ADF41513_MAX_REF_FREQ_HZ)
+ return dev_err_probe(dev, -ERANGE,
+ "reference frequency %u Hz out of range\n",
+ st->ref_freq_hz);
+
+ ret = adf41513_parse_fw(st);
+ if (ret)
+ return ret;
+
+ ret = devm_regulator_bulk_get_enable(dev,
+ ARRAY_SIZE(adf41513_power_supplies),
+ adf41513_power_supplies);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to get and enable regulators\n");
+
+ st->chip_enable = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_HIGH);
+ if (IS_ERR(st->chip_enable))
+ return dev_err_probe(dev, PTR_ERR(st->chip_enable),
+ "fail to request chip enable GPIO\n");
+
+ st->lock_detect = devm_gpiod_get_optional(dev, "lock-detect", GPIOD_IN);
+ if (IS_ERR(st->lock_detect))
+ return dev_err_probe(dev, PTR_ERR(st->lock_detect),
+ "fail to request lock detect GPIO\n");
+
+ ret = devm_mutex_init(dev, &st->lock);
+ if (ret)
+ return ret;
+
+ indio_dev->name = st->chip_info->name;
+ indio_dev->info = &adf41513_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = &adf41513_chan;
+ indio_dev->num_channels = 1;
+
+ ret = adf41513_setup(st);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to setup device\n");
+
+ ret = devm_add_action_or_reset(dev, adf41513_power_down, st);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add power down action\n");
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct spi_device_id adf41513_id[] = {
+ {"adf41510", (kernel_ulong_t)&adf41510_chip_info},
+ {"adf41513", (kernel_ulong_t)&adf41513_chip_info},
+ { }
+};
+MODULE_DEVICE_TABLE(spi, adf41513_id);
+
+static const struct of_device_id adf41513_of_match[] = {
+ { .compatible = "adi,adf41510", .data = &adf41510_chip_info },
+ { .compatible = "adi,adf41513", .data = &adf41513_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adf41513_of_match);
+
+static DEFINE_SIMPLE_DEV_PM_OPS(adf41513_pm_ops, adf41513_pm_suspend, adf41513_pm_resume);
+
+static struct spi_driver adf41513_driver = {
+ .driver = {
+ .name = "adf41513",
+ .pm = pm_ptr(&adf41513_pm_ops),
+ .of_match_table = adf41513_of_match,
+ },
+ .probe = adf41513_probe,
+ .id_table = adf41513_id,
+};
+module_spi_driver(adf41513_driver);
+
+MODULE_AUTHOR("Rodrigo Alencar <rodrigo.alencar@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADF41513 PLL Frequency Synthesizer");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related
* [PATCH v9 7/9] iio: frequency: adf41513: features on frequency change
From: Rodrigo Alencar via B4 Relay @ 2026-03-20 16:27 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar
In-Reply-To: <20260320-adf41513-iio-driver-v9-0-132f0d076374@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Set Bleed current when PFD frequency changes (bleed enabled when in
fractional mode). Set lock detector window size, handling bias and
precision. Add phase resync support, setting clock dividers when
PFD frequency changes.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/frequency/adf41513.c | 100 +++++++++++++++++++++++++++++++++++++++
1 file changed, 100 insertions(+)
diff --git a/drivers/iio/frequency/adf41513.c b/drivers/iio/frequency/adf41513.c
index caae9d53c3e2..f6ed42df26d6 100644
--- a/drivers/iio/frequency/adf41513.c
+++ b/drivers/iio/frequency/adf41513.c
@@ -20,6 +20,7 @@
#include <linux/property.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
+#include <linux/time64.h>
#include <linux/types.h>
#include <linux/units.h>
@@ -213,6 +214,7 @@ struct adf41513_chip_info {
struct adf41513_data {
u64 power_up_frequency_hz;
u64 freq_resolution_uhz;
+ u32 phase_resync_period_ns;
u32 charge_pump_voltage_mv;
u32 lock_detect_count;
@@ -271,6 +273,16 @@ struct adf41513_state {
struct adf41513_pll_settings settings;
};
+static const u16 adf41513_ld_window_x10_ns[] = {
+ 9, 12, 16, 17, 21, 28, 29, 35, /* 0 - 7 */
+ 43, 47, 49, 52, 70, 79, 115, /* 8 - 14 */
+};
+
+static const u8 adf41513_ldp_bias[] = {
+ 0xC, 0xD, 0xE, 0x8, 0x9, 0x4, 0xA, 0x5, /* 0 - 7 */
+ 0x0, 0x6, 0xB, 0x1, 0x2, 0x7, 0x3, /* 8 - 14 */
+};
+
static const char * const adf41513_power_supplies[] = {
"avdd1", "avdd2", "avdd3", "avdd4", "avdd5", "vp",
};
@@ -578,9 +590,82 @@ static int adf41513_calc_pll_settings(struct adf41513_state *st,
return 0;
}
+static void adf41513_set_bleed_val(struct adf41513_state *st)
+{
+ u32 bleed_value, cp_index;
+
+ if (st->data.phase_detector_polarity)
+ bleed_value = 90;
+ else
+ bleed_value = 144;
+
+ cp_index = 1 + FIELD_GET(ADF41513_REG5_CP_CURRENT_MSK,
+ st->regs[ADF41513_REG5]);
+ bleed_value = div64_u64(st->settings.pfd_frequency_uhz * cp_index * bleed_value,
+ 1600ULL * MEGA * MICROHZ_PER_HZ);
+
+ FIELD_MODIFY(ADF41513_REG6_BLEED_CURRENT_MSK, &st->regs[ADF41513_REG6],
+ bleed_value);
+}
+
+static void adf41513_set_ld_window(struct adf41513_state *st)
+{
+ /*
+ * The ideal lock detector window size is halfway between the max
+ * window, set by the phase comparison period t_PFD = (1 / f_PFD),
+ * and the minimum is set by (I_BLEED/I_CP) × t_PFD
+ */
+ u16 ld_window_10x_ns = div64_u64(10ULL * NSEC_PER_SEC * MICROHZ_PER_HZ,
+ st->settings.pfd_frequency_uhz << 1);
+ u8 ld_idx, ldp, ld_bias;
+
+ if (st->settings.mode != ADF41513_MODE_INTEGER_N) {
+ /* account for bleed current (deduced from eq.6 and eq.7) */
+ if (st->data.phase_detector_polarity)
+ ld_window_10x_ns += 4;
+ else
+ ld_window_10x_ns += 6;
+ }
+
+ ld_idx = find_closest(ld_window_10x_ns, adf41513_ld_window_x10_ns,
+ ARRAY_SIZE(adf41513_ld_window_x10_ns));
+ ldp = (adf41513_ldp_bias[ld_idx] >> 2) & 0x3;
+ ld_bias = adf41513_ldp_bias[ld_idx] & 0x3;
+
+ FIELD_MODIFY(ADF41513_REG6_LDP_MSK, &st->regs[ADF41513_REG6], ldp);
+ FIELD_MODIFY(ADF41513_REG9_LD_BIAS_MSK, &st->regs[ADF41513_REG9], ld_bias);
+}
+
+static void adf41513_set_phase_resync(struct adf41513_state *st)
+{
+ u32 total_div, clk1_div, clk2_div;
+
+ if (!st->data.phase_resync_period_ns)
+ return;
+
+ /* assuming both clock dividers hold similar values */
+ total_div = mul_u64_u64_div_u64(st->settings.pfd_frequency_uhz,
+ st->data.phase_resync_period_ns,
+ 1ULL * MICROHZ_PER_HZ * NSEC_PER_SEC);
+ clk1_div = clamp(int_sqrt(total_div), 1,
+ ADF41513_MAX_CLK_DIVIDER);
+ clk2_div = clamp(DIV_ROUND_CLOSEST(total_div, clk1_div), 1,
+ ADF41513_MAX_CLK_DIVIDER);
+
+ FIELD_MODIFY(ADF41513_REG5_CLK1_DIV_MSK, &st->regs[ADF41513_REG5],
+ clk1_div);
+ FIELD_MODIFY(ADF41513_REG7_CLK2_DIV_MSK, &st->regs[ADF41513_REG7],
+ clk2_div);
+
+ /* enable phase resync */
+ st->regs[ADF41513_REG7] |= ADF41513_REG7_CLK_DIV_MODE_MSK;
+}
+
static int adf41513_set_frequency(struct adf41513_state *st, u64 freq_uhz, u16 sync_mask)
{
struct adf41513_pll_settings result;
+ bool pfd_change = false;
+ bool mode_change = false;
int ret;
ret = adf41513_calc_pll_settings(st, &result, freq_uhz);
@@ -588,6 +673,8 @@ static int adf41513_set_frequency(struct adf41513_state *st, u64 freq_uhz, u16 s
return ret;
/* apply computed results to pll settings */
+ pfd_change = st->settings.pfd_frequency_uhz != result.pfd_frequency_uhz;
+ mode_change = st->settings.mode != result.mode;
st->settings = result;
dev_dbg(&st->spi->dev,
@@ -629,6 +716,14 @@ static int adf41513_set_frequency(struct adf41513_state *st, u64 freq_uhz, u16 s
st->regs[ADF41513_REG6] |= ADF41513_REG6_BLEED_ENABLE_MSK;
}
+ if (pfd_change) {
+ adf41513_set_bleed_val(st);
+ adf41513_set_phase_resync(st);
+ }
+
+ if (pfd_change || mode_change)
+ adf41513_set_ld_window(st);
+
return adf41513_sync_config(st, sync_mask | ADF41513_SYNC_REG0);
}
@@ -938,6 +1033,11 @@ static int adf41513_parse_fw(struct adf41513_state *st)
st->data.phase_detector_polarity =
device_property_read_bool(dev, "adi,phase-detector-polarity-positive-enable");
+ st->data.phase_resync_period_ns = 0;
+ ret = device_property_read_u32(dev, "adi,phase-resync-period-ns", &tmp);
+ if (!ret)
+ st->data.phase_resync_period_ns = tmp;
+
st->data.logic_lvl_1v8_en = device_property_read_bool(dev, "adi,logic-level-1v8-enable");
tmp = ADF41513_LD_COUNT_MIN;
--
2.43.0
^ permalink raw reply related
* [PATCH v9 8/9] docs: iio: add documentation for adf41513 driver
From: Rodrigo Alencar via B4 Relay @ 2026-03-20 16:27 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar
In-Reply-To: <20260320-adf41513-iio-driver-v9-0-132f0d076374@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add documentation for ADF41513 driver, which describes the device
driver files and shows how userspace may consume the ABI for various
tasks.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
Documentation/iio/adf41513.rst | 199 +++++++++++++++++++++++++++++++++++++++++
Documentation/iio/index.rst | 1 +
MAINTAINERS | 1 +
3 files changed, 201 insertions(+)
diff --git a/Documentation/iio/adf41513.rst b/Documentation/iio/adf41513.rst
new file mode 100644
index 000000000000..4193c825b532
--- /dev/null
+++ b/Documentation/iio/adf41513.rst
@@ -0,0 +1,199 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+===============
+ADF41513 driver
+===============
+
+This driver supports Analog Devices' ADF41513 and similar SPI PLL frequency
+synthesizers.
+
+1. Supported devices
+====================
+
+* `ADF41510 <https://www.analog.com/ADF41510>`_
+* `ADF41513 <https://www.analog.com/ADF41513>`_
+
+The ADF41513 is an ultralow noise frequency synthesizer that can be used to
+implement local oscillators (LOs) as high as 26.5 GHz in the upconversion and
+downconversion sections of wireless receivers and transmitters. The ADF41510
+is a similar device that supports frequencies up to 10 GHz.
+
+Both devices support integer-N and fractional-N operation modes, providing
+excellent phase noise performance and flexible frequency generation
+capabilities.
+
+Key Features:
+
+- **ADF41510**: 1 GHz to 10 GHz frequency range
+- **ADF41513**: 1 GHz to 26.5 GHz frequency range
+- Integer-N and fractional-N operation modes
+- Ultra-low phase noise (-235 dBc/Hz integer-N, -231 dBc/Hz fractional-N)
+- High maximum PFD frequency (250 MHz integer-N, 125 MHz fractional-N)
+- 25-bit fixed modulus or 49-bit variable modulus fractional modes
+- Programmable charge pump currents with 16x range
+- Digital lock detect functionality
+- Phase resync capability for consistent output phase
+
+2. Device attributes
+====================
+
+The ADF41513 driver provides the following IIO extended attributes for
+frequency control and monitoring:
+
+Each IIO device has a device folder under ``/sys/bus/iio/devices/iio:deviceX``,
+where X is the IIO index of the device. Under these folders reside a set of
+device files that provide access to the synthesizer's functionality.
+
+The following table shows the ADF41513 related device files:
+
++----------------------+-------------------------------------------------------+
+| Device file | Description |
++======================+=======================================================+
+| frequency | RF output frequency control and readback (Hz) |
++----------------------+-------------------------------------------------------+
+| frequency_resolution | Target frequency resolution control (Hz) |
++----------------------+-------------------------------------------------------+
+| powerdown | Power management control (0=active, 1=power down) |
++----------------------+-------------------------------------------------------+
+| phase | RF output phase adjustment and readback (radians) |
++----------------------+-------------------------------------------------------+
+
+2.1 Frequency Control
+----------------------
+
+The ``frequency`` attribute controls the RF output frequency with sub-Hz
+precision. The driver automatically selects between integer-N and fractional-N
+modes to achieve the requested frequency with the best possible phase noise
+performance.
+
+**Supported ranges:**
+
+- **ADF41510**: 1,000,000,000 Hz to 10,000,000,000 Hz (1 GHz to 10 GHz)
+- **ADF41513**: 1,000,000,000 Hz to 26,500,000,000 Hz (1 GHz to 26.5 GHz)
+
+The frequency is specified in Hz, for sub-Hz precision use decimal notation.
+For example, 12.102 GHz would be written as "12102000000.000000".
+
+2.2 Frequency Resolution Control
+--------------------------------
+
+The ``frequency_resolution`` attribute controls the target frequency resolution
+that the driver attempts to achieve. This affects the choice between integer-N
+and fractional-N modes, including fixed modulus (25-bit) and variable modulus
+(49-bit) fractional-N modes:
+
+- **Integer-N**: Resolution = f_PFD
+- **Fixed modulus**: Resolution = f_PFD / 2^25 (~3 Hz with 100 MHz PFD)
+- **Variable modulus**: Resolution = f_PFD / 2^49 (µHz resolution possible)
+
+Default resolution is 1 Hz (1,000,000 µHz).
+
+2.3 Phase adjustment
+--------------------
+
+The ``phase`` attribute allows adjustment of the output phase in radians.
+Setting this attribute enables phase adjustment. It can be set from 0 to 2*pi
+radians. Reading this attribute returns the current phase offset of the output
+signal. To create a consistent phase relationship with the reference signal,
+the phase resync feature needs to be enabled by setting a non-zero value to the
+``adi,phase-resync-period-ns`` device property, which triggers a phase
+resynchronization after locking is achieved.
+
+3. Operating modes
+==================
+
+3.1 Integer-N Mode
+------------------
+
+When the requested frequency can be achieved as an integer multiple of the PFD
+frequency (within the specified resolution tolerance), the driver automatically
+selects integer-N mode for optimal phase noise performance.
+
+In integer-N mode:
+
+- Phase noise: -235 dBc/Hz normalized floor
+- Frequency resolution: f_PFD (same as PFD frequency)
+- Maximum PFD frequency: 250 MHz
+- Bleed current: Disabled
+
+3.2 Fractional-N Mode
+---------------------
+
+When sub-integer frequency steps are required, the driver automatically selects
+fractional-N mode using either fixed or variable modulus.
+
+**Fixed Modulus (25-bit)**:
+
+- Used when variable modulus is not required
+- Resolution: f_PFD / 2^25
+- Simpler implementation, faster settling
+
+**Variable Modulus (49-bit)**:
+
+- Used for maximum resolution requirements
+- Resolution: f_PFD / 2^49 (theoretical)
+- Exact frequency synthesis capability
+
+In fractional-N mode:
+
+- Phase noise: -231 dBc/Hz normalized floor
+- Maximum PFD frequency: 125 MHz
+- Bleed current: Automatically enabled and optimized
+- Dithering: Enabled to reduce fractional spurs
+
+3.3 Automatic Mode Selection
+----------------------------
+
+The driver automatically selects the optimal operating mode based on:
+
+1. **Frequency accuracy requirements**: Determined by frequency_resolution setting
+2. **Phase noise optimization**: Integer-N preferred when possible
+3. **PFD frequency constraints**: Different limits for integer vs fractional modes
+4. **Prescaler selection**: Automatic 4/5 vs 8/9 prescaler selection based on frequency
+
+4. Usage examples
+=================
+
+4.1 Basic Frequency Setting
+----------------------------
+
+Set output frequency to 12.102 GHz:
+
+.. code-block:: bash
+
+ root:/sys/bus/iio/devices/iio:device0> echo 12102000000 > out_altvoltage0_frequency
+
+Read current frequency:
+
+.. code-block:: bash
+
+ root:/sys/bus/iio/devices/iio:device0> cat out_altvoltage0_frequency
+ 12101999999.582767
+
+4.2 High Resolution Frequency Control
+-------------------------------------
+
+Configure for sub-Hz resolution and set a precise frequency:
+
+.. code-block:: bash
+
+ # Set resolution to 0.1 Hz (100,000 µHz)
+ root:/sys/bus/iio/devices/iio:device0> echo 0.1 > out_altvoltage0_frequency_resolution
+
+ # Set frequency to 12.102 GHz (1 µHz precision)
+ root:/sys/bus/iio/devices/iio:device0> echo 12102000000 > out_altvoltage0_frequency
+ root:/sys/bus/iio/devices/iio:device0> cat out_altvoltage0_frequency
+ 12101999999.980131
+
+4.3 Monitor Lock Status
+-----------------------
+
+When lock detect GPIO is configured, check if PLL is locked:
+
+.. code-block:: bash
+
+ # Read frequency - will return error if not locked
+ root:/sys/bus/iio/devices/iio:device0> cat out_altvoltage0_frequency
+
+If the PLL is not locked, the frequency read will return ``-EBUSY`` (Device or
+resource busy).
diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst
index ba3e609c6a13..605871765c78 100644
--- a/Documentation/iio/index.rst
+++ b/Documentation/iio/index.rst
@@ -30,6 +30,7 @@ Industrial I/O Kernel Drivers
ad7625
ad7944
ade9000
+ adf41513
adis16475
adis16480
adis16550
diff --git a/MAINTAINERS b/MAINTAINERS
index fb723e40f1a9..d0573165edad 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1660,6 +1660,7 @@ L: linux-iio@vger.kernel.org
S: Supported
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/frequency/adi,adf41513.yaml
+F: Documentation/iio/adf41513.rst
F: drivers/iio/frequency/adf41513.c
ANALOG DEVICES INC ADF4377 DRIVER
--
2.43.0
^ permalink raw reply related
* [PATCH v9 6/9] iio: frequency: adf41513: handle LE synchronization feature
From: Rodrigo Alencar via B4 Relay @ 2026-03-20 16:27 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar
In-Reply-To: <20260320-adf41513-iio-driver-v9-0-132f0d076374@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
When LE sync is enabled, it is must be set after powering up and must be
disabled when powering down. It is recommended when using the PLL as
a frequency synthesizer, where reference signal will always be present
while the device is being configured.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/frequency/adf41513.c | 35 ++++++++++++++++++++++++++++++++---
1 file changed, 32 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/frequency/adf41513.c b/drivers/iio/frequency/adf41513.c
index 5a0682667d1f..caae9d53c3e2 100644
--- a/drivers/iio/frequency/adf41513.c
+++ b/drivers/iio/frequency/adf41513.c
@@ -222,6 +222,7 @@ struct adf41513_data {
bool phase_detector_polarity;
bool logic_lvl_1v8_en;
+ bool le_sync_en;
};
struct adf41513_pll_settings {
@@ -634,13 +635,27 @@ static int adf41513_set_frequency(struct adf41513_state *st, u64 freq_uhz, u16 s
static int adf41513_suspend(struct adf41513_state *st)
{
st->regs[ADF41513_REG6] |= FIELD_PREP(ADF41513_REG6_POWER_DOWN_MSK, 1);
+ st->regs[ADF41513_REG12] &= ~ADF41513_REG12_LE_SELECT_MSK;
return adf41513_sync_config(st, ADF41513_SYNC_DIFF);
}
static int adf41513_resume(struct adf41513_state *st)
{
+ int ret;
+
st->regs[ADF41513_REG6] &= ~ADF41513_REG6_POWER_DOWN_MSK;
- return adf41513_sync_config(st, ADF41513_SYNC_DIFF);
+ ret = adf41513_sync_config(st, ADF41513_SYNC_DIFF);
+ if (ret)
+ return ret;
+
+ if (st->data.le_sync_en) {
+ st->regs[ADF41513_REG12] |= ADF41513_REG12_LE_SELECT_MSK;
+ ret = adf41513_sync_config(st, ADF41513_SYNC_DIFF);
+ if (ret)
+ return ret;
+ }
+
+ return ret;
}
static ssize_t adf41513_read_uhz(struct iio_dev *indio_dev,
@@ -933,6 +948,8 @@ static int adf41513_parse_fw(struct adf41513_state *st)
"invalid lock detect count: %u\n", tmp);
st->data.lock_detect_count = tmp;
+ /* load enable sync */
+ st->data.le_sync_en = device_property_read_bool(dev, "adi,le-sync-enable");
st->data.freq_resolution_uhz = MICROHZ_PER_HZ;
return 0;
@@ -940,6 +957,7 @@ static int adf41513_parse_fw(struct adf41513_state *st)
static int adf41513_setup(struct adf41513_state *st)
{
+ int ret;
u32 tmp;
memset(st->regs_hw, 0xFF, sizeof(st->regs_hw));
@@ -973,8 +991,19 @@ static int adf41513_setup(struct adf41513_state *st)
st->data.logic_lvl_1v8_en ? 0 : 1);
/* perform initialization sequence with power-up frequency */
- return adf41513_set_frequency(st, st->data.power_up_frequency_hz * MICRO,
- ADF41513_SYNC_ALL);
+ ret = adf41513_set_frequency(st, st->data.power_up_frequency_hz * MICRO,
+ ADF41513_SYNC_ALL);
+ if (ret)
+ return ret;
+
+ if (st->data.le_sync_en) {
+ st->regs[ADF41513_REG12] |= ADF41513_REG12_LE_SELECT_MSK;
+ ret = adf41513_sync_config(st, ADF41513_SYNC_DIFF);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
}
static void adf41513_power_down(void *data)
--
2.43.0
^ permalink raw reply related
* [PATCH v9 9/9] Documentation: ABI: testing: add common ABI file for iio/frequency
From: Rodrigo Alencar via B4 Relay @ 2026-03-20 16:27 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar
In-Reply-To: <20260320-adf41513-iio-driver-v9-0-132f0d076374@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add ABI documentation file for PLL/DDS devices with frequency_resolution
sysfs entry attribute used by both ADF4350 and ADF41513.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
Documentation/ABI/testing/sysfs-bus-iio-frequency | 11 +++++++++++
Documentation/ABI/testing/sysfs-bus-iio-frequency-adf4350 | 10 ----------
2 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-bus-iio-frequency b/Documentation/ABI/testing/sysfs-bus-iio-frequency
new file mode 100644
index 000000000000..1ce8ae578fd6
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-iio-frequency
@@ -0,0 +1,11 @@
+What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_frequency_resolution
+KernelVersion: 6.20
+Contact: linux-iio@vger.kernel.org
+Description:
+ Stores channel Y frequency resolution/channel spacing in Hz for PLL
+ devices. The given value directly influences the operating mode when
+ fractional-N synthesis is required, as it derives values for
+ configurable modulus parameters used in the calculation of the output
+ frequency. It is assumed that the algorithm that is used to compute
+ the various dividers, is able to generate proper values for multiples
+ of channel spacing.
diff --git a/Documentation/ABI/testing/sysfs-bus-iio-frequency-adf4350 b/Documentation/ABI/testing/sysfs-bus-iio-frequency-adf4350
index 1254457a726e..76987a119feb 100644
--- a/Documentation/ABI/testing/sysfs-bus-iio-frequency-adf4350
+++ b/Documentation/ABI/testing/sysfs-bus-iio-frequency-adf4350
@@ -1,13 +1,3 @@
-What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_frequency_resolution
-KernelVersion: 3.4.0
-Contact: linux-iio@vger.kernel.org
-Description:
- Stores channel Y frequency resolution/channel spacing in Hz.
- The value given directly influences the MODULUS used by
- the fractional-N PLL. It is assumed that the algorithm
- that is used to compute the various dividers, is able to
- generate proper values for multiples of channel spacing.
-
What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_refin_frequency
KernelVersion: 3.4.0
Contact: linux-iio@vger.kernel.org
--
2.43.0
^ permalink raw reply related
* [PATCH v2 03/20] drm/mode-config: Mention drm_mode_config_reset() culprits
From: Maxime Ripard @ 2026-03-20 16:27 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Dmitry Baryshkov, Jyri Sarha,
Tomi Valkeinen, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Simon Ser,
Harry Wentland, Melissa Wen, Sebastian Wick, Alex Hung,
Jani Nikula, Rodrigo Vivi, Joonas Lahtinen, Tvrtko Ursulin,
Chen-Yu Tsai, Samuel Holland, Dave Stevenson, Maíra Canal,
Raspberry Pi Kernel Maintenance
Cc: dri-devel, linux-doc, linux-kernel, Daniel Stone, intel-gfx,
intel-xe, linux-arm-kernel, linux-sunxi, Maxime Ripard,
Laurent Pinchart
In-Reply-To: <20260320-drm-mode-config-init-v2-0-c63f1134e76c@kernel.org>
drm_mode_config_reset() does not reset drm_private_states by design.
This is especially significant for the DP MST and tunneling code that
expect to be preserved across a suspend/resume cycle, where
drm_mode_config_reset() is also used.
Let's document this expectation.
Link: https://lore.kernel.org/dri-devel/aOaQLx-7EpsHRwkH@ideak-desk/
Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_mode_config.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c
index 66f7dc37b5970c0a08f8dde008aef56376c59f37..cba527571ca66d3aa6dc652c87e03a19815d1d41 100644
--- a/drivers/gpu/drm/drm_mode_config.c
+++ b/drivers/gpu/drm/drm_mode_config.c
@@ -187,10 +187,14 @@ int drm_mode_getresources(struct drm_device *dev, void *data,
* @dev: drm device
*
* This functions calls all the crtc's, encoder's and connector's ->reset
* callback. Drivers can use this in e.g. their driver load or resume code to
* reset hardware and software state.
+ *
+ * Note that @drm_private_obj structures are expected to be stable across
+ * suspend/resume cycles, and @drm_mode_config_reset() does not affect these
+ * structures.
*/
void drm_mode_config_reset(struct drm_device *dev)
{
struct drm_crtc *crtc;
struct drm_colorop *colorop;
--
2.53.0
^ permalink raw reply related
* [PATCH v2 04/20] drm/colorop: Rename __drm_colorop_state_reset()
From: Maxime Ripard @ 2026-03-20 16:27 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Dmitry Baryshkov, Jyri Sarha,
Tomi Valkeinen, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Simon Ser,
Harry Wentland, Melissa Wen, Sebastian Wick, Alex Hung,
Jani Nikula, Rodrigo Vivi, Joonas Lahtinen, Tvrtko Ursulin,
Chen-Yu Tsai, Samuel Holland, Dave Stevenson, Maíra Canal,
Raspberry Pi Kernel Maintenance
Cc: dri-devel, linux-doc, linux-kernel, Daniel Stone, intel-gfx,
intel-xe, linux-arm-kernel, linux-sunxi, Maxime Ripard
In-Reply-To: <20260320-drm-mode-config-init-v2-0-c63f1134e76c@kernel.org>
__drm_colorop_state_reset() is used to initialize a newly allocated
drm_colorop_state, and is being typically called by drm_colorop_reset().
Since we want to consolidate DRM objects state allocation around the
atomic_create_state callback that will only allocate and initialize a
new drm_colorop_state instance, we will need to call
__drm_colorop_state_reset() from both the reset and atomic_create paths.
To avoid any confusion, we can thus rename __drm_colorop_state_reset()
to __drm_colorop_state_init().
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_colorop.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/drm_colorop.c b/drivers/gpu/drm/drm_colorop.c
index 373cd0ddb8fd4478874509ed12c95451c1f66203..6a26b83b260e8d8e83c703ecde490a7a8740ebfb 100644
--- a/drivers/gpu/drm/drm_colorop.c
+++ b/drivers/gpu/drm/drm_colorop.c
@@ -498,19 +498,19 @@ void drm_colorop_atomic_destroy_state(struct drm_colorop *colorop,
__drm_atomic_helper_colorop_destroy_state(state);
kfree(state);
}
/**
- * __drm_colorop_state_reset - resets colorop state to default values
+ * __drm_colorop_state_init - Initializes colorop state to default values
* @colorop_state: atomic colorop state, must not be NULL
* @colorop: colorop object, must not be NULL
*
* Initializes the newly allocated @colorop_state with default
* values. This is useful for drivers that subclass the colorop state.
*/
-static void __drm_colorop_state_reset(struct drm_colorop_state *colorop_state,
- struct drm_colorop *colorop)
+static void __drm_colorop_state_init(struct drm_colorop_state *colorop_state,
+ struct drm_colorop *colorop)
{
u64 val;
colorop_state->colorop = colorop;
colorop_state->bypass = true;
@@ -537,11 +537,11 @@ static void __drm_colorop_state_reset(struct drm_colorop_state *colorop_state,
*/
static void __drm_colorop_reset(struct drm_colorop *colorop,
struct drm_colorop_state *colorop_state)
{
if (colorop_state)
- __drm_colorop_state_reset(colorop_state, colorop);
+ __drm_colorop_state_init(colorop_state, colorop);
colorop->state = colorop_state;
}
void drm_colorop_reset(struct drm_colorop *colorop)
--
2.53.0
^ permalink raw reply related
* [PATCH v2 05/20] drm/colorop: Create drm_atomic_helper_colorop_create_state()
From: Maxime Ripard @ 2026-03-20 16:27 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Dmitry Baryshkov, Jyri Sarha,
Tomi Valkeinen, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Simon Ser,
Harry Wentland, Melissa Wen, Sebastian Wick, Alex Hung,
Jani Nikula, Rodrigo Vivi, Joonas Lahtinen, Tvrtko Ursulin,
Chen-Yu Tsai, Samuel Holland, Dave Stevenson, Maíra Canal,
Raspberry Pi Kernel Maintenance
Cc: dri-devel, linux-doc, linux-kernel, Daniel Stone, intel-gfx,
intel-xe, linux-arm-kernel, linux-sunxi, Maxime Ripard
In-Reply-To: <20260320-drm-mode-config-init-v2-0-c63f1134e76c@kernel.org>
Commit 47b5ac7daa46 ("drm/atomic: Add new atomic_create_state callback
to drm_private_obj") introduced a new pattern for allocating drm object
states.
Instead of relying on the reset() callback, it created a new
atomic_create_state hook. This is helpful because reset is a bit
overloaded: it's used to create the initial software state, reset it,
but also reset the hardware.
It can also be used either at probe time, to create the initial state
and possibly reset the hardware to an expected default, but also during
suspend/resume.
Both these cases come with different expectations too: during the
initialization, we want to initialize all states, but during
suspend/resume, drm_private_states for example are expected to be kept
around.
And reset() isn't fallible, which makes it harder to handle
initialization errors properly.
It's also only relevant for some drivers, since all the helpers for
reset only create a new state, and don't touch the hardware at all.
It was thus decided to create a new hook that would allocate and
initialize a pristine state without any side effect: atomic_create_state
to untangle a bit some of it, and to separate the
initialization with the actual reset one might need during a
suspend/resume.
Let's continue the transition to the new pattern with drm_colorop.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_colorop.c | 23 +++++++++++++++++++++++
include/drm/drm_colorop.h | 2 ++
2 files changed, 25 insertions(+)
diff --git a/drivers/gpu/drm/drm_colorop.c b/drivers/gpu/drm/drm_colorop.c
index 6a26b83b260e8d8e83c703ecde490a7a8740ebfb..7bfaf2617ec315f42d80ac72a5eaaef868e65657 100644
--- a/drivers/gpu/drm/drm_colorop.c
+++ b/drivers/gpu/drm/drm_colorop.c
@@ -521,10 +521,33 @@ static void __drm_colorop_state_init(struct drm_colorop_state *colorop_state,
&val);
colorop_state->curve_1d_type = val;
}
}
+/**
+ * drm_atomic_helper_colorop_create_state - Allocates and initializes colorop atomic state
+ * @colorop: drm colorop
+ *
+ * Initializes a pristine @drm_colorop_state.
+ *
+ * RETURNS:
+ * Pointer to new colorop state, or ERR_PTR on failure.
+ */
+struct drm_colorop_state *
+drm_atomic_helper_colorop_create_state(struct drm_colorop *colorop)
+{
+ struct drm_colorop_state *state;
+
+ state = kzalloc_obj(*state);
+ if (!state)
+ return ERR_PTR(-ENOMEM);
+
+ __drm_colorop_state_init(state, colorop);
+
+ return state;
+}
+
/**
* __drm_colorop_reset - reset state on colorop
* @colorop: drm colorop
* @colorop_state: colorop state to assign
*
diff --git a/include/drm/drm_colorop.h b/include/drm/drm_colorop.h
index bd082854ca74cac90b42020b09206a8516687666..874ed693329c0ecf94567c094744fe86fd08e382 100644
--- a/include/drm/drm_colorop.h
+++ b/include/drm/drm_colorop.h
@@ -423,10 +423,12 @@ int drm_plane_colorop_3dlut_init(struct drm_device *dev, struct drm_colorop *col
struct drm_plane *plane, const struct drm_colorop_funcs *funcs,
uint32_t lut_size,
enum drm_colorop_lut3d_interpolation_type interpolation,
uint32_t flags);
+struct drm_colorop_state *
+drm_atomic_helper_colorop_create_state(struct drm_colorop *colorop);
struct drm_colorop_state *
drm_atomic_helper_colorop_duplicate_state(struct drm_colorop *colorop);
void drm_colorop_atomic_destroy_state(struct drm_colorop *colorop,
struct drm_colorop_state *state);
--
2.53.0
^ permalink raw reply related
* [PATCH v2 06/20] drm/atomic-state-helper: Fix __drm_atomic_helper_plane_reset() doc typo
From: Maxime Ripard @ 2026-03-20 16:27 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Dmitry Baryshkov, Jyri Sarha,
Tomi Valkeinen, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Simon Ser,
Harry Wentland, Melissa Wen, Sebastian Wick, Alex Hung,
Jani Nikula, Rodrigo Vivi, Joonas Lahtinen, Tvrtko Ursulin,
Chen-Yu Tsai, Samuel Holland, Dave Stevenson, Maíra Canal,
Raspberry Pi Kernel Maintenance
Cc: dri-devel, linux-doc, linux-kernel, Daniel Stone, intel-gfx,
intel-xe, linux-arm-kernel, linux-sunxi, Maxime Ripard,
Laurent Pinchart
In-Reply-To: <20260320-drm-mode-config-init-v2-0-c63f1134e76c@kernel.org>
A typo has slipped through in the __drm_atomic_helper_plane_reset()
documentation, probably due to copy and paste. It will not assign
drm_crtc state pointer, but rather the drm_plane's.
Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic_state_helper.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
index 875149494b00e1eb7481e87d7d7038103b72b7e3..e1f3d05ad3470154a6be01423979a4deb8617441 100644
--- a/drivers/gpu/drm/drm_atomic_state_helper.c
+++ b/drivers/gpu/drm/drm_atomic_state_helper.c
@@ -303,11 +303,11 @@ EXPORT_SYMBOL(__drm_atomic_helper_plane_state_reset);
* __drm_atomic_helper_plane_reset - reset state on plane
* @plane: drm plane
* @plane_state: plane state to assign
*
* Initializes the newly allocated @plane_state and assigns it to
- * the &drm_crtc->state pointer of @plane, usually required when
+ * the &drm_plane->state pointer of @plane, usually required when
* initializing the drivers or when called from the &drm_plane_funcs.reset
* hook.
*
* This is useful for drivers that subclass the plane state.
*/
--
2.53.0
^ permalink raw reply related
* [PATCH v2 07/20] drm/atomic-state-helper: Rename __drm_atomic_helper_plane_state_reset()
From: Maxime Ripard @ 2026-03-20 16:27 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Dmitry Baryshkov, Jyri Sarha,
Tomi Valkeinen, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Simon Ser,
Harry Wentland, Melissa Wen, Sebastian Wick, Alex Hung,
Jani Nikula, Rodrigo Vivi, Joonas Lahtinen, Tvrtko Ursulin,
Chen-Yu Tsai, Samuel Holland, Dave Stevenson, Maíra Canal,
Raspberry Pi Kernel Maintenance
Cc: dri-devel, linux-doc, linux-kernel, Daniel Stone, intel-gfx,
intel-xe, linux-arm-kernel, linux-sunxi, Maxime Ripard,
Laurent Pinchart
In-Reply-To: <20260320-drm-mode-config-init-v2-0-c63f1134e76c@kernel.org>
__drm_atomic_helper_plane_state_reset() is used to initialize a newly
allocated drm_plane_state, and is being typically called by the
drm_plane_funcs.reset implementation.
Since we want to consolidate DRM objects state allocation around the
atomic_create_state callback that will only allocate and initialize a
new drm_plane_state instance, we will need to call
__drm_atomic_helper_plane_state_reset() from both the reset and
atomic_create hooks.
To avoid any confusion, we can thus rename
__drm_atomic_helper_plane_state_reset() to
__drm_atomic_helper_plane_state_init().
Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic_state_helper.c | 10 +++++-----
drivers/gpu/drm/i915/display/intel_plane.c | 2 +-
include/drm/drm_atomic_state_helper.h | 2 +-
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
index e1f3d05ad3470154a6be01423979a4deb8617441..2548d6da13675f63304dc92423c5d225de0447a8 100644
--- a/drivers/gpu/drm/drm_atomic_state_helper.c
+++ b/drivers/gpu/drm/drm_atomic_state_helper.c
@@ -235,19 +235,19 @@ void drm_atomic_helper_crtc_destroy_state(struct drm_crtc *crtc,
kfree(state);
}
EXPORT_SYMBOL(drm_atomic_helper_crtc_destroy_state);
/**
- * __drm_atomic_helper_plane_state_reset - resets plane state to default values
+ * __drm_atomic_helper_plane_state_init - Init plane state to default values
* @plane_state: atomic plane state, must not be NULL
* @plane: plane object, must not be NULL
*
* Initializes the newly allocated @plane_state with default
* values. This is useful for drivers that subclass the CRTC state.
*/
-void __drm_atomic_helper_plane_state_reset(struct drm_plane_state *plane_state,
- struct drm_plane *plane)
+void __drm_atomic_helper_plane_state_init(struct drm_plane_state *plane_state,
+ struct drm_plane *plane)
{
u64 val;
plane_state->plane = plane;
plane_state->rotation = DRM_MODE_ROTATE_0;
@@ -295,11 +295,11 @@ void __drm_atomic_helper_plane_state_reset(struct drm_plane_state *plane_state,
plane->hotspot_y_property,
&val))
plane_state->hotspot_y = val;
}
}
-EXPORT_SYMBOL(__drm_atomic_helper_plane_state_reset);
+EXPORT_SYMBOL(__drm_atomic_helper_plane_state_init);
/**
* __drm_atomic_helper_plane_reset - reset state on plane
* @plane: drm plane
* @plane_state: plane state to assign
@@ -313,11 +313,11 @@ EXPORT_SYMBOL(__drm_atomic_helper_plane_state_reset);
*/
void __drm_atomic_helper_plane_reset(struct drm_plane *plane,
struct drm_plane_state *plane_state)
{
if (plane_state)
- __drm_atomic_helper_plane_state_reset(plane_state, plane);
+ __drm_atomic_helper_plane_state_init(plane_state, plane);
plane->state = plane_state;
}
EXPORT_SYMBOL(__drm_atomic_helper_plane_reset);
diff --git a/drivers/gpu/drm/i915/display/intel_plane.c b/drivers/gpu/drm/i915/display/intel_plane.c
index e06a0618b4c6cd3b4d13752f604006b525118193..705367fef836663e50fe9bbfb1fcc83abf1fb249 100644
--- a/drivers/gpu/drm/i915/display/intel_plane.c
+++ b/drivers/gpu/drm/i915/display/intel_plane.c
@@ -65,11 +65,11 @@
static void intel_plane_state_reset(struct intel_plane_state *plane_state,
struct intel_plane *plane)
{
memset(plane_state, 0, sizeof(*plane_state));
- __drm_atomic_helper_plane_state_reset(&plane_state->uapi, &plane->base);
+ __drm_atomic_helper_plane_state_init(&plane_state->uapi, &plane->base);
plane_state->scaler_id = -1;
}
struct intel_plane *intel_plane_alloc(void)
diff --git a/include/drm/drm_atomic_state_helper.h b/include/drm/drm_atomic_state_helper.h
index 900672c6ea90ba9cb87e38a7c84225972aee43c5..44e8850aae7fd6390f4b58188a9c677b8389702f 100644
--- a/include/drm/drm_atomic_state_helper.h
+++ b/include/drm/drm_atomic_state_helper.h
@@ -51,11 +51,11 @@ struct drm_crtc_state *
drm_atomic_helper_crtc_duplicate_state(struct drm_crtc *crtc);
void __drm_atomic_helper_crtc_destroy_state(struct drm_crtc_state *state);
void drm_atomic_helper_crtc_destroy_state(struct drm_crtc *crtc,
struct drm_crtc_state *state);
-void __drm_atomic_helper_plane_state_reset(struct drm_plane_state *state,
+void __drm_atomic_helper_plane_state_init(struct drm_plane_state *state,
struct drm_plane *plane);
void __drm_atomic_helper_plane_reset(struct drm_plane *plane,
struct drm_plane_state *state);
void drm_atomic_helper_plane_reset(struct drm_plane *plane);
void __drm_atomic_helper_plane_duplicate_state(struct drm_plane *plane,
--
2.53.0
^ permalink raw reply related
* [PATCH v2 08/20] drm/plane: Add new atomic_create_state callback
From: Maxime Ripard @ 2026-03-20 16:27 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Dmitry Baryshkov, Jyri Sarha,
Tomi Valkeinen, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Simon Ser,
Harry Wentland, Melissa Wen, Sebastian Wick, Alex Hung,
Jani Nikula, Rodrigo Vivi, Joonas Lahtinen, Tvrtko Ursulin,
Chen-Yu Tsai, Samuel Holland, Dave Stevenson, Maíra Canal,
Raspberry Pi Kernel Maintenance
Cc: dri-devel, linux-doc, linux-kernel, Daniel Stone, intel-gfx,
intel-xe, linux-arm-kernel, linux-sunxi, Maxime Ripard
In-Reply-To: <20260320-drm-mode-config-init-v2-0-c63f1134e76c@kernel.org>
Commit 47b5ac7daa46 ("drm/atomic: Add new atomic_create_state callback
to drm_private_obj") introduced a new pattern for allocating drm object
states.
Instead of relying on the reset() callback, it created a new
atomic_create_state hook. This is helpful because reset is a bit
overloaded: it's used to create the initial software state, reset it,
but also reset the hardware.
It can also be used either at probe time, to create the initial state
and possibly reset the hardware to an expected default, but also during
suspend/resume.
Both these cases come with different expectations too: during the
initialization, we want to initialize all states, but during
suspend/resume, drm_private_states for example are expected to be kept
around.
And reset() isn't fallible, which makes it harder to handle
initialization errors properly.
And this is only really relevant for some drivers, since all the helpers
for reset only create a new state, and don't touch the hardware at all.
It was thus decided to create a new hook that would allocate and
initialize a pristine state without any side effect:
atomic_create_state to untangle a bit some of it, and to separate the
initialization with the actual reset one might need during a
suspend/resume.
Let's continue the transition to the new pattern with planes.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic_state_helper.c | 44 +++++++++++++++++++++++++++++++
drivers/gpu/drm/drm_mode_config.c | 21 ++++++++++++++-
include/drm/drm_atomic_state_helper.h | 4 +++
include/drm/drm_plane.h | 13 +++++++++
4 files changed, 81 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
index 2548d6da13675f63304dc92423c5d225de0447a8..f4ce9d3573cbecf216904db54335e0cf84a01c39 100644
--- a/drivers/gpu/drm/drm_atomic_state_helper.c
+++ b/drivers/gpu/drm/drm_atomic_state_helper.c
@@ -319,10 +319,29 @@ void __drm_atomic_helper_plane_reset(struct drm_plane *plane,
plane->state = plane_state;
}
EXPORT_SYMBOL(__drm_atomic_helper_plane_reset);
+/**
+ * __drm_atomic_helper_plane_create_state - initializes plane state
+ * @plane: plane object
+ * @state: new state to initialize
+ *
+ * Initializes the newly allocated @state, usually required when
+ * initializing the drivers.
+ *
+ * @state is assumed to be zeroed.
+ *
+ * This is useful for drivers that subclass @drm_plane_state.
+ */
+void __drm_atomic_helper_plane_create_state(struct drm_plane *plane,
+ struct drm_plane_state *state)
+{
+ __drm_atomic_helper_plane_state_init(state, plane);
+}
+EXPORT_SYMBOL(__drm_atomic_helper_plane_create_state);
+
/**
* drm_atomic_helper_plane_reset - default &drm_plane_funcs.reset hook for planes
* @plane: drm plane
*
* Resets the atomic state for @plane by freeing the state pointer (which might
@@ -338,10 +357,35 @@ void drm_atomic_helper_plane_reset(struct drm_plane *plane)
if (plane->state)
__drm_atomic_helper_plane_reset(plane, plane->state);
}
EXPORT_SYMBOL(drm_atomic_helper_plane_reset);
+/**
+ * drm_atomic_helper_plane_create_state - default &drm_plane_funcs.atomic_create_state hook for planes
+ * @plane: plane object
+ *
+ * Initializes a pristine @drm_plane_state.
+ *
+ * This is useful for drivers that don't subclass @drm_plane_state.
+ *
+ * RETURNS:
+ * Pointer to new plane state, or ERR_PTR on failure.
+ */
+struct drm_plane_state *drm_atomic_helper_plane_create_state(struct drm_plane *plane)
+{
+ struct drm_plane_state *state;
+
+ state = kzalloc_obj(*state);
+ if (!state)
+ return ERR_PTR(-ENOMEM);
+
+ __drm_atomic_helper_plane_create_state(plane, state);
+
+ return state;
+}
+EXPORT_SYMBOL(drm_atomic_helper_plane_create_state);
+
/**
* __drm_atomic_helper_plane_duplicate_state - copy atomic plane state
* @plane: plane object
* @state: atomic plane state
*
diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c
index cba527571ca66d3aa6dc652c87e03a19815d1d41..09b8292195ba5eb5d96735aee5506407bd32ade3 100644
--- a/drivers/gpu/drm/drm_mode_config.c
+++ b/drivers/gpu/drm/drm_mode_config.c
@@ -180,10 +180,26 @@ int drm_mode_getresources(struct drm_device *dev, void *data,
drm_connector_list_iter_end(&conn_iter);
return ret;
}
+static int drm_mode_config_plane_create_state(struct drm_plane *plane)
+{
+ struct drm_plane_state *plane_state;
+
+ if (!plane->funcs->atomic_create_state)
+ return 0;
+
+ plane_state = plane->funcs->atomic_create_state(plane);
+ if (IS_ERR(plane_state))
+ return PTR_ERR(plane_state);
+
+ plane->state = plane_state;
+
+ return 0;
+}
+
/**
* drm_mode_config_reset - call ->reset callbacks
* @dev: drm device
*
* This functions calls all the crtc's, encoder's and connector's ->reset
@@ -204,13 +220,16 @@ void drm_mode_config_reset(struct drm_device *dev)
struct drm_connector_list_iter conn_iter;
drm_for_each_colorop(colorop, dev)
drm_colorop_reset(colorop);
- drm_for_each_plane(plane, dev)
+ drm_for_each_plane(plane, dev) {
if (plane->funcs->reset)
plane->funcs->reset(plane);
+ else if (plane->funcs->atomic_create_state)
+ drm_mode_config_plane_create_state(plane);
+ }
drm_for_each_crtc(crtc, dev)
if (crtc->funcs->reset)
crtc->funcs->reset(crtc);
diff --git a/include/drm/drm_atomic_state_helper.h b/include/drm/drm_atomic_state_helper.h
index 44e8850aae7fd6390f4b58188a9c677b8389702f..6a3a2feb3dff1f2fbdf2a6e63d8d7317c7d6ead6 100644
--- a/include/drm/drm_atomic_state_helper.h
+++ b/include/drm/drm_atomic_state_helper.h
@@ -53,10 +53,14 @@ void __drm_atomic_helper_crtc_destroy_state(struct drm_crtc_state *state);
void drm_atomic_helper_crtc_destroy_state(struct drm_crtc *crtc,
struct drm_crtc_state *state);
void __drm_atomic_helper_plane_state_init(struct drm_plane_state *state,
struct drm_plane *plane);
+void __drm_atomic_helper_plane_create_state(struct drm_plane *plane,
+ struct drm_plane_state *state);
+struct drm_plane_state *
+drm_atomic_helper_plane_create_state(struct drm_plane *plane);
void __drm_atomic_helper_plane_reset(struct drm_plane *plane,
struct drm_plane_state *state);
void drm_atomic_helper_plane_reset(struct drm_plane *plane);
void __drm_atomic_helper_plane_duplicate_state(struct drm_plane *plane,
struct drm_plane_state *state);
diff --git a/include/drm/drm_plane.h b/include/drm/drm_plane.h
index 703ef4d1bbbcf084c43aa5e127d28691878061c4..4d4d511b681d50c17fbc593cce9f706d63e04a52 100644
--- a/include/drm/drm_plane.h
+++ b/include/drm/drm_plane.h
@@ -386,10 +386,23 @@ struct drm_plane_funcs {
* 0 on success or a negative error code on failure.
*/
int (*set_property)(struct drm_plane *plane,
struct drm_property *property, uint64_t val);
+ /**
+ * @atomic_create_state:
+ *
+ * Allocates a pristine, initialized, state for the plane object
+ * and returns it.
+ *
+ * RETURNS:
+ *
+ * A new, pristine, plane state instance or an error pointer
+ * on failure.
+ */
+ struct drm_plane_state *(*atomic_create_state)(struct drm_plane *plane);
+
/**
* @atomic_duplicate_state:
*
* Duplicate the current atomic state for this plane and return it.
* The core and helpers guarantee that any atomic state duplicated with
--
2.53.0
^ permalink raw reply related
* [PATCH v2 09/20] drm/atomic-state-helper: Rename __drm_atomic_helper_crtc_state_reset()
From: Maxime Ripard @ 2026-03-20 16:27 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Dmitry Baryshkov, Jyri Sarha,
Tomi Valkeinen, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Simon Ser,
Harry Wentland, Melissa Wen, Sebastian Wick, Alex Hung,
Jani Nikula, Rodrigo Vivi, Joonas Lahtinen, Tvrtko Ursulin,
Chen-Yu Tsai, Samuel Holland, Dave Stevenson, Maíra Canal,
Raspberry Pi Kernel Maintenance
Cc: dri-devel, linux-doc, linux-kernel, Daniel Stone, intel-gfx,
intel-xe, linux-arm-kernel, linux-sunxi, Maxime Ripard,
Laurent Pinchart
In-Reply-To: <20260320-drm-mode-config-init-v2-0-c63f1134e76c@kernel.org>
__drm_atomic_helper_crtc_state_reset() is used to initialize a newly
allocated drm_crtc_state, and is being typically called by the
drm_crtc_funcs.reset implementation.
Since we want to consolidate DRM objects state allocation around the
atomic_create_state callback that will only allocate and initialize a
new drm_crtc_state instance, we will need to call
__drm_atomic_helper_crtc_state_reset() from both the reset and
atomic_create hooks.
To avoid any confusion, we can thus rename
__drm_atomic_helper_crtc_state_reset() to
__drm_atomic_helper_crtc_state_init().
Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic_state_helper.c | 10 +++++-----
drivers/gpu/drm/i915/display/intel_crtc.c | 2 +-
include/drm/drm_atomic_state_helper.h | 2 +-
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
index f4ce9d3573cbecf216904db54335e0cf84a01c39..b87cc3cd5b0021699ac57a33739d172a5706e2e1 100644
--- a/drivers/gpu/drm/drm_atomic_state_helper.c
+++ b/drivers/gpu/drm/drm_atomic_state_helper.c
@@ -61,25 +61,25 @@
* For other drivers the building blocks are split out, see the documentation
* for these functions.
*/
/**
- * __drm_atomic_helper_crtc_state_reset - reset the CRTC state
+ * __drm_atomic_helper_crtc_state_init - Initializes the CRTC state
* @crtc_state: atomic CRTC state, must not be NULL
* @crtc: CRTC object, must not be NULL
*
* Initializes the newly allocated @crtc_state with default
* values. This is useful for drivers that subclass the CRTC state.
*/
void
-__drm_atomic_helper_crtc_state_reset(struct drm_crtc_state *crtc_state,
- struct drm_crtc *crtc)
+__drm_atomic_helper_crtc_state_init(struct drm_crtc_state *crtc_state,
+ struct drm_crtc *crtc)
{
crtc_state->crtc = crtc;
crtc_state->background_color = DRM_ARGB64_PREP(0xffff, 0, 0, 0);
}
-EXPORT_SYMBOL(__drm_atomic_helper_crtc_state_reset);
+EXPORT_SYMBOL(__drm_atomic_helper_crtc_state_init);
/**
* __drm_atomic_helper_crtc_reset - reset state on CRTC
* @crtc: drm CRTC
* @crtc_state: CRTC state to assign
@@ -94,11 +94,11 @@ EXPORT_SYMBOL(__drm_atomic_helper_crtc_state_reset);
void
__drm_atomic_helper_crtc_reset(struct drm_crtc *crtc,
struct drm_crtc_state *crtc_state)
{
if (crtc_state)
- __drm_atomic_helper_crtc_state_reset(crtc_state, crtc);
+ __drm_atomic_helper_crtc_state_init(crtc_state, crtc);
if (drm_dev_has_vblank(crtc->dev))
drm_crtc_vblank_reset(crtc);
crtc->state = crtc_state;
diff --git a/drivers/gpu/drm/i915/display/intel_crtc.c b/drivers/gpu/drm/i915/display/intel_crtc.c
index 53378d2dcbec867e7973cb3b874802f2ada01d41..b57a59a5f7786f1029d576b0f3d564e77b7060e4 100644
--- a/drivers/gpu/drm/i915/display/intel_crtc.c
+++ b/drivers/gpu/drm/i915/display/intel_crtc.c
@@ -179,11 +179,11 @@ struct intel_crtc_state *intel_crtc_state_alloc(struct intel_crtc *crtc)
void intel_crtc_state_reset(struct intel_crtc_state *crtc_state,
struct intel_crtc *crtc)
{
memset(crtc_state, 0, sizeof(*crtc_state));
- __drm_atomic_helper_crtc_state_reset(&crtc_state->uapi, &crtc->base);
+ __drm_atomic_helper_crtc_state_init(&crtc_state->uapi, &crtc->base);
crtc_state->cpu_transcoder = INVALID_TRANSCODER;
crtc_state->master_transcoder = INVALID_TRANSCODER;
crtc_state->hsw_workaround_pipe = INVALID_PIPE;
crtc_state->scaler_state.scaler_id = -1;
diff --git a/include/drm/drm_atomic_state_helper.h b/include/drm/drm_atomic_state_helper.h
index 6a3a2feb3dff1f2fbdf2a6e63d8d7317c7d6ead6..161cd1f6b8266e882eb1fd128b403cba350be3d2 100644
--- a/include/drm/drm_atomic_state_helper.h
+++ b/include/drm/drm_atomic_state_helper.h
@@ -38,11 +38,11 @@ struct drm_connector_state;
struct drm_private_obj;
struct drm_private_state;
struct drm_modeset_acquire_ctx;
struct drm_device;
-void __drm_atomic_helper_crtc_state_reset(struct drm_crtc_state *state,
+void __drm_atomic_helper_crtc_state_init(struct drm_crtc_state *state,
struct drm_crtc *crtc);
void __drm_atomic_helper_crtc_reset(struct drm_crtc *crtc,
struct drm_crtc_state *state);
void drm_atomic_helper_crtc_reset(struct drm_crtc *crtc);
void __drm_atomic_helper_crtc_duplicate_state(struct drm_crtc *crtc,
--
2.53.0
^ permalink raw reply related
* [PATCH v2 10/20] drm/crtc: Add new atomic_create_state callback
From: Maxime Ripard @ 2026-03-20 16:27 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Dmitry Baryshkov, Jyri Sarha,
Tomi Valkeinen, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Simon Ser,
Harry Wentland, Melissa Wen, Sebastian Wick, Alex Hung,
Jani Nikula, Rodrigo Vivi, Joonas Lahtinen, Tvrtko Ursulin,
Chen-Yu Tsai, Samuel Holland, Dave Stevenson, Maíra Canal,
Raspberry Pi Kernel Maintenance
Cc: dri-devel, linux-doc, linux-kernel, Daniel Stone, intel-gfx,
intel-xe, linux-arm-kernel, linux-sunxi, Maxime Ripard
In-Reply-To: <20260320-drm-mode-config-init-v2-0-c63f1134e76c@kernel.org>
Commit 47b5ac7daa46 ("drm/atomic: Add new atomic_create_state callback
to drm_private_obj") introduced a new pattern for allocating drm object
states.
Instead of relying on the reset() callback, it created a new
atomic_create_state hook. This is helpful because reset is a bit
overloaded: it's used to create the initial software tate, reset it, but
also reset the hardware.
It can also be used either at probe time, to create the initial state
and possibly reset the hardware to an expected default, but also during
suspend/resume.
Both these cases come with different expectations too: during the
initialization, we want to initialize all states, but during
suspend/resume, drm_private_states for example are expected to be kept
around.
And reset() isn't fallible, which makes it harder to handle
initialization errors properly.
And this is only really relevant for some drivers, since all the helpers
for reset only create a new state, and don't touch the hardware at all.
It was thus decided to create a new hook that would allocate and
initialize a pristine state without any side effect:
atomic_create_state to untangle a bit some of it, and to separate the
initialization with the actual reset one might need during a
suspend/resume.
Let's continue the transition to the new pattern with crtcs.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic_state_helper.c | 47 +++++++++++++++++++++++++++++++
drivers/gpu/drm/drm_mode_config.c | 21 +++++++++++++-
include/drm/drm_atomic_state_helper.h | 4 +++
include/drm/drm_crtc.h | 13 +++++++++
4 files changed, 84 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
index b87cc3cd5b0021699ac57a33739d172a5706e2e1..f1fc2edc6e6e35fbac5ef82437f3cb7485afc412 100644
--- a/drivers/gpu/drm/drm_atomic_state_helper.c
+++ b/drivers/gpu/drm/drm_atomic_state_helper.c
@@ -103,10 +103,32 @@ __drm_atomic_helper_crtc_reset(struct drm_crtc *crtc,
crtc->state = crtc_state;
}
EXPORT_SYMBOL(__drm_atomic_helper_crtc_reset);
+/**
+ * __drm_atomic_helper_crtc_create_state - initializes crtc state
+ * @crtc: crtc object
+ * @state: new state to initialize
+ *
+ * Initializes the newly allocated @state, usually required when
+ * initializing the drivers.
+ *
+ * @state is assumed to be zeroed.
+ *
+ * This is useful for drivers that subclass @drm_crtc_state.
+ */
+void __drm_atomic_helper_crtc_create_state(struct drm_crtc *crtc,
+ struct drm_crtc_state *state)
+{
+ __drm_atomic_helper_crtc_state_init(state, crtc);
+
+ if (drm_dev_has_vblank(crtc->dev))
+ drm_crtc_vblank_reset(crtc);
+}
+EXPORT_SYMBOL(__drm_atomic_helper_crtc_create_state);
+
/**
* drm_atomic_helper_crtc_reset - default &drm_crtc_funcs.reset hook for CRTCs
* @crtc: drm CRTC
*
* Resets the atomic state for @crtc by freeing the state pointer (which might
@@ -122,10 +144,35 @@ void drm_atomic_helper_crtc_reset(struct drm_crtc *crtc)
__drm_atomic_helper_crtc_reset(crtc, crtc_state);
}
EXPORT_SYMBOL(drm_atomic_helper_crtc_reset);
+/**
+ * drm_atomic_helper_crtc_create_state - default &drm_crtc_funcs.atomic_create_state hook for crtcs
+ * @crtc: crtc object
+ *
+ * Initializes a pristine @drm_crtc_state.
+ *
+ * This is useful for drivers that don't subclass @drm_crtc_state.
+ *
+ * RETURNS:
+ * Pointer to new crtc state, or ERR_PTR on failure.
+ */
+struct drm_crtc_state *drm_atomic_helper_crtc_create_state(struct drm_crtc *crtc)
+{
+ struct drm_crtc_state *state;
+
+ state = kzalloc_obj(*state);
+ if (!state)
+ return ERR_PTR(-ENOMEM);
+
+ __drm_atomic_helper_crtc_create_state(crtc, state);
+
+ return state;
+}
+EXPORT_SYMBOL(drm_atomic_helper_crtc_create_state);
+
/**
* __drm_atomic_helper_crtc_duplicate_state - copy atomic CRTC state
* @crtc: CRTC object
* @state: atomic CRTC state
*
diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c
index 09b8292195ba5eb5d96735aee5506407bd32ade3..f5092d03b168bb114a199b1dbcaeb770983c9749 100644
--- a/drivers/gpu/drm/drm_mode_config.c
+++ b/drivers/gpu/drm/drm_mode_config.c
@@ -196,10 +196,26 @@ static int drm_mode_config_plane_create_state(struct drm_plane *plane)
plane->state = plane_state;
return 0;
}
+static int drm_mode_config_crtc_create_state(struct drm_crtc *crtc)
+{
+ struct drm_crtc_state *crtc_state;
+
+ if (!crtc->funcs->atomic_create_state)
+ return 0;
+
+ crtc_state = crtc->funcs->atomic_create_state(crtc);
+ if (IS_ERR(crtc_state))
+ return PTR_ERR(crtc_state);
+
+ crtc->state = crtc_state;
+
+ return 0;
+}
+
/**
* drm_mode_config_reset - call ->reset callbacks
* @dev: drm device
*
* This functions calls all the crtc's, encoder's and connector's ->reset
@@ -227,13 +243,16 @@ void drm_mode_config_reset(struct drm_device *dev)
plane->funcs->reset(plane);
else if (plane->funcs->atomic_create_state)
drm_mode_config_plane_create_state(plane);
}
- drm_for_each_crtc(crtc, dev)
+ drm_for_each_crtc(crtc, dev) {
if (crtc->funcs->reset)
crtc->funcs->reset(crtc);
+ else if (crtc->funcs->atomic_create_state)
+ drm_mode_config_crtc_create_state(crtc);
+ }
drm_for_each_encoder(encoder, dev)
if (encoder->funcs && encoder->funcs->reset)
encoder->funcs->reset(encoder);
diff --git a/include/drm/drm_atomic_state_helper.h b/include/drm/drm_atomic_state_helper.h
index 161cd1f6b8266e882eb1fd128b403cba350be3d2..bc83094dfb78f0f3699ca7956a8a666ca09b2d1a 100644
--- a/include/drm/drm_atomic_state_helper.h
+++ b/include/drm/drm_atomic_state_helper.h
@@ -43,10 +43,14 @@ struct drm_device;
void __drm_atomic_helper_crtc_state_init(struct drm_crtc_state *state,
struct drm_crtc *crtc);
void __drm_atomic_helper_crtc_reset(struct drm_crtc *crtc,
struct drm_crtc_state *state);
void drm_atomic_helper_crtc_reset(struct drm_crtc *crtc);
+void __drm_atomic_helper_crtc_create_state(struct drm_crtc *crtc,
+ struct drm_crtc_state *state);
+struct drm_crtc_state *
+drm_atomic_helper_crtc_create_state(struct drm_crtc *crtc);
void __drm_atomic_helper_crtc_duplicate_state(struct drm_crtc *crtc,
struct drm_crtc_state *state);
struct drm_crtc_state *
drm_atomic_helper_crtc_duplicate_state(struct drm_crtc *crtc);
void __drm_atomic_helper_crtc_destroy_state(struct drm_crtc_state *state);
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index 312fc1e745d226adec787cd5f1b7c923fe629670..9e410191683c4cb978d759e3b734afdd3bb8aa76 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -636,10 +636,23 @@ struct drm_crtc_funcs {
* 0 on success or a negative error code on failure.
*/
int (*set_property)(struct drm_crtc *crtc,
struct drm_property *property, uint64_t val);
+ /**
+ * @atomic_create_state:
+ *
+ * Allocates a pristine, initialized, state for the crtc object
+ * and returns it.
+ *
+ * RETURNS:
+ *
+ * A new, pristine, crtc state instance or an error pointer
+ * on failure.
+ */
+ struct drm_crtc_state *(*atomic_create_state)(struct drm_crtc *crtc);
+
/**
* @atomic_duplicate_state:
*
* Duplicate the current atomic state for this CRTC and return it.
* The core and helpers guarantee that any atomic state duplicated with
--
2.53.0
^ permalink raw reply related
* [PATCH v2 11/20] drm/atomic-state-helper: Rename __drm_atomic_helper_connector_state_reset()
From: Maxime Ripard @ 2026-03-20 16:27 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Dmitry Baryshkov, Jyri Sarha,
Tomi Valkeinen, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Simon Ser,
Harry Wentland, Melissa Wen, Sebastian Wick, Alex Hung,
Jani Nikula, Rodrigo Vivi, Joonas Lahtinen, Tvrtko Ursulin,
Chen-Yu Tsai, Samuel Holland, Dave Stevenson, Maíra Canal,
Raspberry Pi Kernel Maintenance
Cc: dri-devel, linux-doc, linux-kernel, Daniel Stone, intel-gfx,
intel-xe, linux-arm-kernel, linux-sunxi, Maxime Ripard,
Laurent Pinchart
In-Reply-To: <20260320-drm-mode-config-init-v2-0-c63f1134e76c@kernel.org>
__drm_atomic_helper_connector_state_reset() is used to initialize a
newly allocated drm_connector_state, and is being typically called by
the drm_connector_funcs.reset implementation.
Since we want to consolidate DRM objects state allocation around the
atomic_create_state callback that will only allocate and initialize a
new drm_connector_state instance, we will need to call
__drm_atomic_helper_connector_state_reset() from both the reset and
atomic_create hooks.
To avoid any confusion, we can thus rename
__drm_atomic_helper_connector_state_reset() to
__drm_atomic_helper_connector_state_init().
Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic_state_helper.c | 10 +++++-----
include/drm/drm_atomic_state_helper.h | 2 +-
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
index f1fc2edc6e6e35fbac5ef82437f3cb7485afc412..8ed8cb8a9048e8fd89fa22d20985c84af1672135 100644
--- a/drivers/gpu/drm/drm_atomic_state_helper.c
+++ b/drivers/gpu/drm/drm_atomic_state_helper.c
@@ -515,24 +515,24 @@ void drm_atomic_helper_plane_destroy_state(struct drm_plane *plane,
kfree(state);
}
EXPORT_SYMBOL(drm_atomic_helper_plane_destroy_state);
/**
- * __drm_atomic_helper_connector_state_reset - reset the connector state
+ * __drm_atomic_helper_connector_state_init - Initializes the connector state
* @conn_state: atomic connector state, must not be NULL
* @connector: connectotr object, must not be NULL
*
* Initializes the newly allocated @conn_state with default
* values. This is useful for drivers that subclass the connector state.
*/
void
-__drm_atomic_helper_connector_state_reset(struct drm_connector_state *conn_state,
- struct drm_connector *connector)
+__drm_atomic_helper_connector_state_init(struct drm_connector_state *conn_state,
+ struct drm_connector *connector)
{
conn_state->connector = connector;
}
-EXPORT_SYMBOL(__drm_atomic_helper_connector_state_reset);
+EXPORT_SYMBOL(__drm_atomic_helper_connector_state_init);
/**
* __drm_atomic_helper_connector_reset - reset state on connector
* @connector: drm connector
* @conn_state: connector state to assign
@@ -547,11 +547,11 @@ EXPORT_SYMBOL(__drm_atomic_helper_connector_state_reset);
void
__drm_atomic_helper_connector_reset(struct drm_connector *connector,
struct drm_connector_state *conn_state)
{
if (conn_state)
- __drm_atomic_helper_connector_state_reset(conn_state, connector);
+ __drm_atomic_helper_connector_state_init(conn_state, connector);
connector->state = conn_state;
}
EXPORT_SYMBOL(__drm_atomic_helper_connector_reset);
diff --git a/include/drm/drm_atomic_state_helper.h b/include/drm/drm_atomic_state_helper.h
index bc83094dfb78f0f3699ca7956a8a666ca09b2d1a..259a39a0f41ddb2b7d6c1fe7c1a2b0b51774254c 100644
--- a/include/drm/drm_atomic_state_helper.h
+++ b/include/drm/drm_atomic_state_helper.h
@@ -72,11 +72,11 @@ struct drm_plane_state *
drm_atomic_helper_plane_duplicate_state(struct drm_plane *plane);
void __drm_atomic_helper_plane_destroy_state(struct drm_plane_state *state);
void drm_atomic_helper_plane_destroy_state(struct drm_plane *plane,
struct drm_plane_state *state);
-void __drm_atomic_helper_connector_state_reset(struct drm_connector_state *conn_state,
+void __drm_atomic_helper_connector_state_init(struct drm_connector_state *conn_state,
struct drm_connector *connector);
void __drm_atomic_helper_connector_reset(struct drm_connector *connector,
struct drm_connector_state *conn_state);
void drm_atomic_helper_connector_reset(struct drm_connector *connector);
void drm_atomic_helper_connector_tv_reset(struct drm_connector *connector);
--
2.53.0
^ permalink raw reply related
* [PATCH v2 12/20] drm/hdmi: Rename __drm_atomic_helper_connector_hdmi_reset()
From: Maxime Ripard @ 2026-03-20 16:27 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Dmitry Baryshkov, Jyri Sarha,
Tomi Valkeinen, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Simon Ser,
Harry Wentland, Melissa Wen, Sebastian Wick, Alex Hung,
Jani Nikula, Rodrigo Vivi, Joonas Lahtinen, Tvrtko Ursulin,
Chen-Yu Tsai, Samuel Holland, Dave Stevenson, Maíra Canal,
Raspberry Pi Kernel Maintenance
Cc: dri-devel, linux-doc, linux-kernel, Daniel Stone, intel-gfx,
intel-xe, linux-arm-kernel, linux-sunxi, Maxime Ripard,
Laurent Pinchart
In-Reply-To: <20260320-drm-mode-config-init-v2-0-c63f1134e76c@kernel.org>
__drm_atomic_helper_connector_hdmi_reset() is typically used to
initialize a newly allocated drm_connector_state when the connector is
using the HDMI helpers, and is being called by the
drm_connector_funcs.reset implementation.
Since we want to consolidate DRM objects state allocation around the
atomic_create_state callback that will only allocate and initialize a
new drm_connector_state instance, we will need to call
__drm_atomic_helper_connector_hdmi_reset() from both the reset and
atomic_create hooks.
To avoid any confusion, we can thus rename
__drm_atomic_helper_connector_hdmi_reset() to
__drm_atomic_helper_connector_hdmi_state_init().
Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/display/drm_bridge_connector.c | 4 ++--
drivers/gpu/drm/display/drm_hdmi_state_helper.c | 14 ++++++++------
drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 2 +-
drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c | 2 +-
drivers/gpu/drm/vc4/vc4_hdmi.c | 2 +-
include/drm/display/drm_hdmi_state_helper.h | 4 ++--
6 files changed, 15 insertions(+), 13 deletions(-)
diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c
index f686aa5c0ed9b84dbe5e0957df22d08aff2f1945..929f06b290626d2091418a6377a5230e3c264b60 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -270,12 +270,12 @@ static void drm_bridge_connector_reset(struct drm_connector *connector)
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
drm_atomic_helper_connector_reset(connector);
if (bridge_connector->bridge_hdmi)
- __drm_atomic_helper_connector_hdmi_reset(connector,
- connector->state);
+ __drm_atomic_helper_connector_hdmi_state_init(connector,
+ connector->state);
}
static const struct drm_connector_funcs drm_bridge_connector_funcs = {
.reset = drm_bridge_connector_reset,
.detect = drm_bridge_connector_detect,
diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
index a1d16762ac7a9ebdc48f081c5d2f5e200d406099..cdd6df37c04a4b2073004ea08a3f5283a56692df 100644
--- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c
+++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
@@ -304,29 +304,31 @@
* --kunitconfig=drivers/gpu/drm/tests \
* drm_atomic_helper_connector_hdmi_*
*/
/**
- * __drm_atomic_helper_connector_hdmi_reset() - Initializes all HDMI @drm_connector_state resources
+ * __drm_atomic_helper_connector_hdmi_state_init() - Initializes all HDMI @drm_connector_state resources
* @connector: DRM connector
* @new_conn_state: connector state to reset
*
* Initializes all HDMI resources from a @drm_connector_state without
* actually allocating it. This is useful for HDMI drivers, in
- * combination with __drm_atomic_helper_connector_reset() or
- * drm_atomic_helper_connector_reset().
+ * combination with __drm_atomic_helper_connector_reset(),
+ * drm_atomic_helper_connector_reset(),
+ * __drm_atomic_helper_connector_create_state() or
+ * drm_atomic_helper_connector_create_state() .
*/
-void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector,
- struct drm_connector_state *new_conn_state)
+void __drm_atomic_helper_connector_hdmi_state_init(struct drm_connector *connector,
+ struct drm_connector_state *new_conn_state)
{
unsigned int max_bpc = connector->max_bpc;
new_conn_state->max_bpc = max_bpc;
new_conn_state->max_requested_bpc = max_bpc;
new_conn_state->hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO;
}
-EXPORT_SYMBOL(__drm_atomic_helper_connector_hdmi_reset);
+EXPORT_SYMBOL(__drm_atomic_helper_connector_hdmi_state_init);
static const struct drm_display_mode *
connector_state_get_mode(const struct drm_connector_state *conn_state)
{
struct drm_atomic_state *state;
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
index a50f260c73e41493528e545591aaad0e3f138be2..0ebebb04e68fab5b7938a604f85ad9b756fe33f6 100644
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
@@ -283,11 +283,11 @@ sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force)
}
static void sun4i_hdmi_connector_reset(struct drm_connector *connector)
{
drm_atomic_helper_connector_reset(connector);
- __drm_atomic_helper_connector_hdmi_reset(connector, connector->state);
+ __drm_atomic_helper_connector_hdmi_state_init(connector, connector->state);
}
static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = {
.detect = sun4i_hdmi_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
diff --git a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
index 4bdcea3c7435146aebb1716f38d7302ddb7c6296..aaa73b75f3ce5010147f48afc365d074bb2ec7a7 100644
--- a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
+++ b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c
@@ -166,11 +166,11 @@ static const struct drm_connector_helper_funcs dummy_connector_helper_funcs = {
};
static void dummy_hdmi_connector_reset(struct drm_connector *connector)
{
drm_atomic_helper_connector_reset(connector);
- __drm_atomic_helper_connector_hdmi_reset(connector, connector->state);
+ __drm_atomic_helper_connector_hdmi_state_init(connector, connector->state);
}
static const struct drm_connector_funcs dummy_connector_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c
index 9898e5451a07a1dca809fb6db3fec59d07485b95..f59d0877a5f48bed6b421775da7ddd008d68f9d9 100644
--- a/drivers/gpu/drm/vc4/vc4_hdmi.c
+++ b/drivers/gpu/drm/vc4/vc4_hdmi.c
@@ -506,11 +506,11 @@ static int vc4_hdmi_connector_atomic_check(struct drm_connector *connector,
}
static void vc4_hdmi_connector_reset(struct drm_connector *connector)
{
drm_atomic_helper_connector_reset(connector);
- __drm_atomic_helper_connector_hdmi_reset(connector, connector->state);
+ __drm_atomic_helper_connector_hdmi_state_init(connector, connector->state);
drm_atomic_helper_connector_tv_margins_reset(connector);
}
static const struct drm_connector_funcs vc4_hdmi_connector_funcs = {
.force = drm_atomic_helper_connector_hdmi_force,
diff --git a/include/drm/display/drm_hdmi_state_helper.h b/include/drm/display/drm_hdmi_state_helper.h
index 2349c0d0f00fb468167023d4db2055c70e603bcd..f7600aabdd5f066772a9a82a1fa8a00be7a48396 100644
--- a/include/drm/display/drm_hdmi_state_helper.h
+++ b/include/drm/display/drm_hdmi_state_helper.h
@@ -9,12 +9,12 @@ struct drm_connector_state;
struct drm_display_mode;
struct hdmi_audio_infoframe;
enum drm_connector_status;
-void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector,
- struct drm_connector_state *new_conn_state);
+void __drm_atomic_helper_connector_hdmi_state_init(struct drm_connector *connector,
+ struct drm_connector_state *new_conn_state);
int drm_atomic_helper_connector_hdmi_check(struct drm_connector *connector,
struct drm_atomic_state *state);
int drm_atomic_helper_connector_hdmi_update_audio_infoframe(struct drm_connector *connector,
--
2.53.0
^ permalink raw reply related
* [PATCH v2 13/20] drm/connector: Add new atomic_create_state callback
From: Maxime Ripard @ 2026-03-20 16:27 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Dmitry Baryshkov, Jyri Sarha,
Tomi Valkeinen, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Simon Ser,
Harry Wentland, Melissa Wen, Sebastian Wick, Alex Hung,
Jani Nikula, Rodrigo Vivi, Joonas Lahtinen, Tvrtko Ursulin,
Chen-Yu Tsai, Samuel Holland, Dave Stevenson, Maíra Canal,
Raspberry Pi Kernel Maintenance
Cc: dri-devel, linux-doc, linux-kernel, Daniel Stone, intel-gfx,
intel-xe, linux-arm-kernel, linux-sunxi, Maxime Ripard
In-Reply-To: <20260320-drm-mode-config-init-v2-0-c63f1134e76c@kernel.org>
Commit 47b5ac7daa46 ("drm/atomic: Add new atomic_create_state callback
to drm_private_obj") introduced a new pattern for allocating drm object
states.
Instead of relying on the reset() callback, it created a new
atomic_create_state hook. This is helpful because reset is a bit
overloaded: it's used to create the initial software tate, reset it, but
also reset the hardware.
It can also be used either at probe time, to create the initial state
and possibly reset the hardware to an expected default, but also during
suspend/resume.
Both these cases come with different expectations too: during the
initialization, we want to initialize all states, but during
suspend/resume, drm_private_states for example are expected to be kept
around.
And reset() isn't fallible, which makes it harder to handle
initialization errors properly.
And this is only really relevant for some drivers, since all the helpers
for reset only create a new state, and don't touch the hardware at all.
It was thus decided to create a new hook that would allocate and
initialize a pristine state without any side effect:
atomic_create_state to untangle a bit some of it, and to separate the
initialization with the actual reset one might need during a
suspend/resume.
Let's continue the transition to the new pattern with connectors.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic_state_helper.c | 45 +++++++++++++++++++++++++++++++
drivers/gpu/drm/drm_mode_config.c | 21 ++++++++++++++-
include/drm/drm_atomic_state_helper.h | 4 +++
include/drm/drm_connector.h | 13 +++++++++
4 files changed, 82 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
index 8ed8cb8a9048e8fd89fa22d20985c84af1672135..c7eb3ce8c282a0d1099bdb840653baa9f2005db5 100644
--- a/drivers/gpu/drm/drm_atomic_state_helper.c
+++ b/drivers/gpu/drm/drm_atomic_state_helper.c
@@ -553,10 +553,29 @@ __drm_atomic_helper_connector_reset(struct drm_connector *connector,
connector->state = conn_state;
}
EXPORT_SYMBOL(__drm_atomic_helper_connector_reset);
+/**
+ * __drm_atomic_helper_connector_create_state - initializes connector state
+ * @connector: connector object
+ * @state: new state to initialize
+ *
+ * Initializes the newly allocated @state, usually required when
+ * initializing the drivers.
+ *
+ * @state is assumed to be zeroed.
+ *
+ * This is useful for drivers that subclass @drm_connector_state.
+ */
+void __drm_atomic_helper_connector_create_state(struct drm_connector *connector,
+ struct drm_connector_state *state)
+{
+ __drm_atomic_helper_connector_state_init(state, connector);
+}
+EXPORT_SYMBOL(__drm_atomic_helper_connector_create_state);
+
/**
* drm_atomic_helper_connector_reset - default &drm_connector_funcs.reset hook for connectors
* @connector: drm connector
*
* Resets the atomic state for @connector by freeing the state pointer (which
@@ -573,10 +592,36 @@ void drm_atomic_helper_connector_reset(struct drm_connector *connector)
kfree(connector->state);
__drm_atomic_helper_connector_reset(connector, conn_state);
}
EXPORT_SYMBOL(drm_atomic_helper_connector_reset);
+/**
+ * drm_atomic_helper_connector_create_state - default &drm_connector_funcs.atomic_create_state hook for connectors
+ * @connector: connector object
+ *
+ * Initializes a pristine @drm_connector_state.
+ *
+ * This is useful for drivers that don't subclass @drm_connector_state.
+ *
+ * RETURNS:
+ * Pointer to new connector state, or ERR_PTR on failure.
+ */
+struct drm_connector_state *
+drm_atomic_helper_connector_create_state(struct drm_connector *connector)
+{
+ struct drm_connector_state *state;
+
+ state = kzalloc_obj(*state);
+ if (!state)
+ return ERR_PTR(-ENOMEM);
+
+ __drm_atomic_helper_connector_create_state(connector, state);
+
+ return state;
+}
+EXPORT_SYMBOL(drm_atomic_helper_connector_create_state);
+
/**
* drm_atomic_helper_connector_tv_margins_reset - Resets TV connector properties
* @connector: DRM connector
*
* Resets the TV-related properties attached to a connector.
diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c
index f5092d03b168bb114a199b1dbcaeb770983c9749..124cef8f0c6eb15c9fde4cf7cee2936cbf2b902f 100644
--- a/drivers/gpu/drm/drm_mode_config.c
+++ b/drivers/gpu/drm/drm_mode_config.c
@@ -212,10 +212,26 @@ static int drm_mode_config_crtc_create_state(struct drm_crtc *crtc)
crtc->state = crtc_state;
return 0;
}
+static int drm_mode_config_connector_create_state(struct drm_connector *connector)
+{
+ struct drm_connector_state *conn_state;
+
+ if (!connector->funcs->atomic_create_state)
+ return 0;
+
+ conn_state = connector->funcs->atomic_create_state(connector);
+ if (IS_ERR(conn_state))
+ return PTR_ERR(conn_state);
+
+ connector->state = conn_state;
+
+ return 0;
+}
+
/**
* drm_mode_config_reset - call ->reset callbacks
* @dev: drm device
*
* This functions calls all the crtc's, encoder's and connector's ->reset
@@ -255,13 +271,16 @@ void drm_mode_config_reset(struct drm_device *dev)
drm_for_each_encoder(encoder, dev)
if (encoder->funcs && encoder->funcs->reset)
encoder->funcs->reset(encoder);
drm_connector_list_iter_begin(dev, &conn_iter);
- drm_for_each_connector_iter(connector, &conn_iter)
+ drm_for_each_connector_iter(connector, &conn_iter) {
if (connector->funcs->reset)
connector->funcs->reset(connector);
+ else if (connector->funcs->atomic_create_state)
+ drm_mode_config_connector_create_state(connector);
+ }
drm_connector_list_iter_end(&conn_iter);
}
EXPORT_SYMBOL(drm_mode_config_reset);
/*
diff --git a/include/drm/drm_atomic_state_helper.h b/include/drm/drm_atomic_state_helper.h
index 259a39a0f41ddb2b7d6c1fe7c1a2b0b51774254c..96a058ba698203c023e54c2b3d908c13cfa2d23b 100644
--- a/include/drm/drm_atomic_state_helper.h
+++ b/include/drm/drm_atomic_state_helper.h
@@ -76,11 +76,15 @@ void drm_atomic_helper_plane_destroy_state(struct drm_plane *plane,
void __drm_atomic_helper_connector_state_init(struct drm_connector_state *conn_state,
struct drm_connector *connector);
void __drm_atomic_helper_connector_reset(struct drm_connector *connector,
struct drm_connector_state *conn_state);
+void __drm_atomic_helper_connector_create_state(struct drm_connector *connector,
+ struct drm_connector_state *state);
void drm_atomic_helper_connector_reset(struct drm_connector *connector);
+struct drm_connector_state *
+drm_atomic_helper_connector_create_state(struct drm_connector *connector);
void drm_atomic_helper_connector_tv_reset(struct drm_connector *connector);
int drm_atomic_helper_connector_tv_check(struct drm_connector *connector,
struct drm_atomic_state *state);
void drm_atomic_helper_connector_tv_margins_reset(struct drm_connector *connector);
void
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index c18be8c19de0ab1e02e7d7b1fcd9f2ab5ef1dd15..6d73068de157144770931c64ce3f91e6f15ef0e4 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -1509,10 +1509,23 @@ struct drm_connector_funcs {
* when a connector is being hot-unplugged for drivers that support
* connector hotplugging (e.g. DisplayPort MST).
*/
void (*destroy)(struct drm_connector *connector);
+ /**
+ * @atomic_create_state:
+ *
+ * Allocates a pristine, initialized, state for the connector object
+ * and returns it.
+ *
+ * RETURNS:
+ *
+ * A new, pristine, connector state instance or an error pointer
+ * on failure.
+ */
+ struct drm_connector_state *(*atomic_create_state)(struct drm_connector *connector);
+
/**
* @atomic_duplicate_state:
*
* Duplicate the current atomic state for this connector and return it.
* The core and helpers guarantee that any atomic state duplicated with
--
2.53.0
^ permalink raw reply related
* [PATCH v2 14/20] drm/mode-config: Create drm_mode_config_create_state()
From: Maxime Ripard @ 2026-03-20 16:27 UTC (permalink / raw)
To: Maarten Lankhorst, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Dmitry Baryshkov, Jyri Sarha,
Tomi Valkeinen, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Simon Ser,
Harry Wentland, Melissa Wen, Sebastian Wick, Alex Hung,
Jani Nikula, Rodrigo Vivi, Joonas Lahtinen, Tvrtko Ursulin,
Chen-Yu Tsai, Samuel Holland, Dave Stevenson, Maíra Canal,
Raspberry Pi Kernel Maintenance
Cc: dri-devel, linux-doc, linux-kernel, Daniel Stone, intel-gfx,
intel-xe, linux-arm-kernel, linux-sunxi, Maxime Ripard
In-Reply-To: <20260320-drm-mode-config-init-v2-0-c63f1134e76c@kernel.org>
drm_mode_config_reset() can be used to create the initial state, but
also to return to the initial state, when doing a suspend/resume cycle
for example.
It also affects both the software and the hardware, and drivers can
choose to reset the hardware as well. Most will just create an empty
state and the synchronisation between hardware and software states will
effectively be done when the first commit is done.
That dual role can be harmful, since some objects do need to be
initialized but also need to be preserved across a suspend/resume cycle.
drm_private_obj are such objects for example.
Thus, let's create another helper for drivers to call to initialize
their state when the driver is loaded, so we can make
drm_mode_config_reset() only about handling suspend/resume and similar.
Signed-off-by: Maxime Ripard <mripard@kernel.org>
---
drivers/gpu/drm/drm_atomic.c | 12 +++++-
drivers/gpu/drm/drm_mode_config.c | 87 +++++++++++++++++++++++++++++++++++++++
include/drm/drm_mode_config.h | 1 +
3 files changed, 98 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 253a00f450b0fb1b7eb6ea42b3235fbf9ee78376..5aa3f9f0620bd155dd53023e6c76e3883ffaadef 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -59,12 +59,20 @@
* when preparing the update and kept alive as long as they are active
* in the device.
*
* Their respective lifetimes are:
*
- * - at reset time, the object reset implementation will allocate a new
- * default state and will store it in the object state pointer.
+ * - at driver initialization time, the driver will allocate an initial,
+ * pristine, state and will store it using
+ * drm_mode_config_create_state(). Historically, this was one of
+ * drm_mode_config_reset() job, so one might still encounter it in a
+ * driver.
+ *
+ * - at reset time, for example during suspend/resume,
+ * drm_mode_config_reset() will reset the software and hardware state
+ * to a known default and will store it in the object's state pointer.
+ * Not all objects are affected by drm_mode_config_reset() though.
*
* - whenever a new update is needed:
*
* + A new &struct drm_atomic_state is allocated using
* drm_atomic_state_alloc().
diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c
index 124cef8f0c6eb15c9fde4cf7cee2936cbf2b902f..61c3ba5d3901ea2bb05bbd1db0644187820a44dd 100644
--- a/drivers/gpu/drm/drm_mode_config.c
+++ b/drivers/gpu/drm/drm_mode_config.c
@@ -21,10 +21,11 @@
*/
#include <linux/export.h>
#include <linux/uaccess.h>
+#include <drm/drm_atomic.h>
#include <drm/drm_drv.h>
#include <drm/drm_encoder.h>
#include <drm/drm_file.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_managed.h>
@@ -281,10 +282,96 @@ void drm_mode_config_reset(struct drm_device *dev)
}
drm_connector_list_iter_end(&conn_iter);
}
EXPORT_SYMBOL(drm_mode_config_reset);
+/**
+ * drm_mode_config_create_state - Allocates the initial state
+ * @dev: drm device
+ *
+ * This functions creates the initial state for all the objects. Drivers
+ * can use this in e.g. probe to initialize their software state.
+ *
+ * It has two main differences with drm_mode_config_reset(): the reset()
+ * hooks aren't called and thus the hardware will be left untouched, but
+ * also the @drm_private_obj structures will be initialized as opposed
+ * to drm_mode_config_reset() that skips them.
+ *
+ * Returns: 0 on success, negative error value on failure.
+ */
+int drm_mode_config_create_state(struct drm_device *dev)
+{
+ struct drm_crtc *crtc;
+ struct drm_colorop *colorop;
+ struct drm_plane *plane;
+ struct drm_connector *connector;
+ struct drm_connector_list_iter conn_iter;
+ struct drm_private_obj *privobj;
+ int ret;
+
+ drm_for_each_privobj(privobj, dev) {
+ struct drm_private_state *privobj_state;
+
+ if (privobj->state)
+ continue;
+
+ if (!privobj->funcs->atomic_create_state)
+ continue;
+
+ privobj_state = privobj->funcs->atomic_create_state(privobj);
+ if (IS_ERR(privobj_state))
+ return PTR_ERR(privobj_state);
+
+ privobj->state = privobj_state;
+ }
+
+ drm_for_each_colorop(colorop, dev) {
+ struct drm_colorop_state *colorop_state;
+
+ if (colorop->state)
+ continue;
+
+ colorop_state = drm_atomic_helper_colorop_create_state(colorop);
+ if (IS_ERR(colorop_state))
+ return PTR_ERR(colorop_state);
+
+ colorop->state = colorop_state;
+ }
+
+ drm_for_each_plane(plane, dev) {
+ if (plane->state)
+ continue;
+
+ ret = drm_mode_config_plane_create_state(plane);
+ if (ret)
+ return ret;
+ }
+
+ drm_for_each_crtc(crtc, dev) {
+ if (crtc->state)
+ continue;
+
+ ret = drm_mode_config_crtc_create_state(crtc);
+ if (ret)
+ return ret;
+ }
+
+ drm_connector_list_iter_begin(dev, &conn_iter);
+ drm_for_each_connector_iter(connector, &conn_iter) {
+ if (connector->state)
+ continue;
+
+ ret = drm_mode_config_connector_create_state(connector);
+ if (ret)
+ return ret;
+ }
+ drm_connector_list_iter_end(&conn_iter);
+
+ return 0;
+}
+EXPORT_SYMBOL(drm_mode_config_create_state);
+
/*
* Global properties
*/
static const struct drm_prop_enum_list drm_plane_type_enum_list[] = {
{ DRM_PLANE_TYPE_OVERLAY, "Overlay" },
diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h
index 687c0ee163d25dfa7cf563e77209dd33e349800b..22c7e767a2e98f99fff9311eb590f62bd4c9c8a9 100644
--- a/include/drm/drm_mode_config.h
+++ b/include/drm/drm_mode_config.h
@@ -1005,9 +1005,10 @@ int __must_check drmm_mode_config_init(struct drm_device *dev);
static inline int drm_mode_config_init(struct drm_device *dev)
{
return drmm_mode_config_init(dev);
}
+int drm_mode_config_create_state(struct drm_device *dev);
void drm_mode_config_reset(struct drm_device *dev);
void drm_mode_config_cleanup(struct drm_device *dev);
#endif
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox