* [PATCH v6 1/8] docs/zh_CN: Add index.rst translation
From: Kefan Bai @ 2026-05-10 13:53 UTC (permalink / raw)
To: linux-usb, si.yanteng
Cc: gregkh, seakeel, alexs, dzm91, corbet, skhan, linux-doc, doubled
In-Reply-To: <cover.1778415392.git.baikefan@leap-io-kernel.com>
Translate .../usb/index.rst into Chinese and update subsystem-apis.rst
Update the translation through commit a592a36e4937
("Documentation: use a source-read extension for the index link boilerplate")
Reviewed-by: Yanteng Si <siyanteng@cqsoftware.com.cn>
Signed-off-by: Kefan Bai <baikefan@leap-io-kernel.com>
---
.../translations/zh_CN/subsystem-apis.rst | 2 +-
.../translations/zh_CN/usb/index.rst | 54 +++++++++++++++++++
2 files changed, 55 insertions(+), 1 deletion(-)
create mode 100644 Documentation/translations/zh_CN/usb/index.rst
diff --git a/Documentation/translations/zh_CN/subsystem-apis.rst b/Documentation/translations/zh_CN/subsystem-apis.rst
index 830217140fb6..b52e1feb0167 100644
--- a/Documentation/translations/zh_CN/subsystem-apis.rst
+++ b/Documentation/translations/zh_CN/subsystem-apis.rst
@@ -90,6 +90,7 @@ TODOList:
security/index
PCI/index
peci/index
+ usb/index
TODOList:
@@ -104,6 +105,5 @@ TODOList:
* accel/index
* crypto/index
* bpf/index
-* usb/index
* misc-devices/index
* wmi/index
diff --git a/Documentation/translations/zh_CN/usb/index.rst b/Documentation/translations/zh_CN/usb/index.rst
new file mode 100644
index 000000000000..7cfe99a4dc0a
--- /dev/null
+++ b/Documentation/translations/zh_CN/usb/index.rst
@@ -0,0 +1,54 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. include:: ../disclaimer-zh_CN.rst
+
+:Original: Documentation/usb/index.rst
+:翻译:
+
+ 白钶凡 Kefan Bai <baikefan@leap-io-kernel.com>
+
+:校译:
+
+
+
+===========
+USB 支持
+===========
+
+.. toctree::
+ :maxdepth: 1
+
+
+Todolist:
+
+* acm
+* authorization
+* chipidea
+* dwc3
+* ehci
+* usbmon
+* functionfs
+* functionfs-desc
+* gadget_configfs
+* gadget_hid
+* gadget_multi
+* gadget_printer
+* gadget_serial
+* gadget_uvc
+* gadget-testing
+* iuu_phoenix
+* mass-storage
+* misc_usbsevseg
+* mtouchusb
+* ohci
+* raw-gadget
+* usbip_protocol
+* usb-serial
+* usb-help
+* text_files
+
+.. only:: subproject and html
+
+ 索引
+ =======
+
+ * :ref:`genindex`
--
2.54.0
^ permalink raw reply related
* Re: [PATCH RFC v5 10/53] KVM: guest_memfd: Add basic support for KVM_SET_MEMORY_ATTRIBUTES2
From: Liam R. Howlett @ 2026-05-10 13:43 UTC (permalink / raw)
To: Ackerley Tng
Cc: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
ira.weiny, jmattson, jthoughton, michael.roth, oupton,
pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
pratyush, suzuki.poulose, aneesh.kumar, Paolo Bonzini,
Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka,
kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco
In-Reply-To: <CAEvNRgF9+Gr7UVEq-E2SQEb_XOQQMOXy9F_A2tA=DbNV_fJ0EQ@mail.gmail.com>
On 7 May 2026 12:56:11 GMT-04:00, Ackerley Tng <ackerleytng@google.com> wrote:
>"Liam R. Howlett" <liam@infradead.org> writes:
>
>> On 26/04/28 04:25PM, Ackerley Tng via B4 Relay wrote:
>>>
>>> [...snip...]
>>>
>>> +/*
>>> + * Preallocate memory for attributes to be stored on a maple tree, pointed to
>>> + * by mas. Adjacent ranges with attributes identical to the new attributes
>>> + * will be merged. Also sets mas's bounds up for storing attributes.
>>> + *
>>> + * This maintains the invariant that ranges with the same attributes will
>>> + * always be merged.
>>> + */
>>> +static int kvm_gmem_mas_preallocate(struct ma_state *mas, u64 attributes,
>>> + pgoff_t start, size_t nr_pages)
>>> +{
>>> + pgoff_t end = start + nr_pages;
>>> + pgoff_t last = end - 1;
>>> + void *entry;
>>> +
>>> + /* Try extending range. entry is NULL on overflow/wrap-around. */
>>> + mas_set_range(mas, end, end);
>>> + entry = mas_find(mas, end);
>
>Thank you for your reviews!
>
>>
>> Please read the documentation as I believe you have a bug here. What
>> happens if there is another range stored higher than end + 1?
>>
>
>The invariant in this maple tree is that contiguous ranges with the same
>attribute are stored as a single range.
>
>The goal of this first part is to get the entry at the index just after
>the requested range, and see what the attribute there is. If that
>attribute is what we're about to set, extend the requested range for
>storing to the end of that range.
>
>If there is another range higher than end + 1, with the invariant
>maintained, that attribute has to be different than the attribute stored
>at end. Hence, we only want to extend this requested range up till end.
>
mas_find() will look for an entry at the given address for the first search, and if it is not found it will continue to search upwards. Since you limit the search to end, it will work as you want and there isn't a bug as I was thinking in my sleep deprived state.
Since you are searching for exactly one address (end), it might serve you better to walk there. Maybe walking is a better API for what you are doing here?
>> Do you have testing of these functions somewhere?
>>
>
>GMEM_CONVERSION_MULTIPAGE_TEST_INIT_SHARED(indexing, 4) tests setting
>attributes in ranges. If test_page is 2,
>
>1. [0, 4) starts off shared (4 is the number of pages in the guest_memfd)
>2. [2, 3) is converted to private
> => so the ranges should now be [0, 2), [2, 3), [3, 4)
>3. [2, 3) is converted back to shared
> => so the ranges should now be [0, 4)
>
>I verified this by inserting some trace_printk()s and inspecting manually.
>
Thanks. I find the exclusive ranges a bit odd to think about in the maple tree context, but this test case makes sense. This is especially odd to look at a single index entry, at least for me.
I generally have a set of test cases and append any bug reproduces to that list so they are unlikely to reoccur. My testing is certainly different from what you'll be doing, but this method has done well with the quality of code improving over time, and limited (if any) regressions.
I actually insist that any fix has a test before I accept them. There are two reasons for this: 1. Avoiding the regression. 2. People really understand the bug if they can create a reproducer.
I hope this helps.
>>> + if (entry && xa_to_value(entry) == attributes)
>>> + last = mas->last;
>>> +
>>> + if (start > 0) {
>>> + mas_set_range(mas, start - 1, start - 1);
>>> + entry = mas_find(mas, start - 1);
>>> + if (entry && xa_to_value(entry) == attributes)
>>> + start = mas->index;
>>> + }
>>> +
>>> + mas_set_range(mas, start, last);
>>> + return mas_preallocate(mas, xa_mk_value(attributes), GFP_KERNEL);
>>> +}
>>> +
>>>
>>> [...snip...]
>>>
^ permalink raw reply
* [PATCH v12 11/11] Documentation: ABI: testing: add common ABI file for iio/frequency
From: Rodrigo Alencar via B4 Relay @ 2026-05-10 12:42 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: <20260510-adf41513-iio-driver-v12-0-34af2ed2779f@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 v12 09/11] iio: frequency: adf41513: features on frequency change
From: Rodrigo Alencar via B4 Relay @ 2026-05-10 12:42 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: <20260510-adf41513-iio-driver-v12-0-34af2ed2779f@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 | 105 +++++++++++++++++++++++++++++++++++++++
1 file changed, 105 insertions(+)
diff --git a/drivers/iio/frequency/adf41513.c b/drivers/iio/frequency/adf41513.c
index 397e859170cc..2014fd1892d9 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>
@@ -144,6 +145,10 @@
#define ADF41513_PRESCALER_8_9 1
#define ADF41513_PRESCALER_AUTO 2
+/* CLK Divider mode */
+#define ADF41513_CLK_DIV_MODE_OFF 0
+#define ADF41513_CLK_DIV_MODE_PHASE_RESYNC 2
+
/* Specifications */
#define ADF41510_MAX_RF_FREQ_HZ (10ULL * HZ_PER_GHZ)
#define ADF41513_MIN_RF_FREQ_HZ (1ULL * HZ_PER_GHZ)
@@ -209,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;
@@ -268,6 +274,16 @@ struct adf41513_state {
bool powerdown;
};
+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 +594,83 @@ 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 */
+ FIELD_MODIFY(ADF41513_REG7_CLK_DIV_MODE_MSK, &st->regs[ADF41513_REG7],
+ ADF41513_CLK_DIV_MODE_PHASE_RESYNC);
+}
+
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 +678,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 +721,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);
}
@@ -908,6 +1008,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 v12 10/11] docs: iio: add documentation for adf41513 driver
From: Rodrigo Alencar via B4 Relay @ 2026-05-10 12:42 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: <20260510-adf41513-iio-driver-v12-0-34af2ed2779f@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..244453cce6f6
--- /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 |
++======================================+=======================================================+
+| out_altvoltage0_frequency | RF output frequency control and readback (Hz) |
++--------------------------------------+-------------------------------------------------------+
+| out_altvoltage0_frequency_resolution | Target frequency resolution control (Hz) |
++--------------------------------------+-------------------------------------------------------+
+| out_altvoltage0_powerdown | Power management control (0=active, 1=power down) |
++--------------------------------------+-------------------------------------------------------+
+| out_altvoltage0_phase | RF output phase adjustment and readback (radians) |
++--------------------------------------+-------------------------------------------------------+
+
+2.1 Frequency Control
+----------------------
+
+The ``out_altvoltage0_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 ``out_altvoltage0_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 = :math:`f_{PFD}` (same as PFD frequency)
+- **Fixed modulus**: Resolution = :math:`f_{PFD} / 2^{25}` (~3 Hz with 100 MHz PFD)
+- **Variable modulus**: Resolution = :math:`f_{PFD} / 2^{49}` (µHz resolution possible)
+
+Default resolution is 1 Hz (1,000,000 µHz).
+
+2.3 Phase adjustment
+--------------------
+
+The ``out_altvoltage0_phase`` attribute allows adjustment of the output phase
+in radians. Setting this attribute enables phase adjustment. It can be set
+from 0 to :math:`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: :math:`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: :math:`f_{PFD} / 2^{25}`
+- Simpler implementation, faster settling
+
+**Variable Modulus (49-bit)**:
+
+- Used for maximum resolution requirements
+- Resolution: :math:`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 007e0a1fcc5a..b02b879b053a 100644
--- a/Documentation/iio/index.rst
+++ b/Documentation/iio/index.rst
@@ -31,6 +31,7 @@ Industrial I/O Kernel Drivers
ad7625
ad7944
ade9000
+ adf41513
adis16475
adis16480
adis16550
diff --git a/MAINTAINERS b/MAINTAINERS
index b5bf5f7de9c9..4c326244d496 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1668,6 +1668,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 v12 08/11] iio: frequency: adf41513: handle LE synchronization feature
From: Rodrigo Alencar via B4 Relay @ 2026-05-10 12:42 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: <20260510-adf41513-iio-driver-v12-0-34af2ed2779f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
When LE sync is enabled, it must be set after powering up and it 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 | 32 ++++++++++++++++++++++++++++++--
1 file changed, 30 insertions(+), 2 deletions(-)
diff --git a/drivers/iio/frequency/adf41513.c b/drivers/iio/frequency/adf41513.c
index b8a8b9fb81d9..397e859170cc 100644
--- a/drivers/iio/frequency/adf41513.c
+++ b/drivers/iio/frequency/adf41513.c
@@ -218,6 +218,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_ALL);
+ ret = adf41513_sync_config(st, 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 ssize_t adf41513_read_resolution(struct iio_dev *indio_dev,
@@ -903,6 +918,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;
@@ -959,7 +976,18 @@ static int adf41513_setup(struct device *dev, struct adf41513_state *st)
if (ret)
return ret;
- return devm_add_action_or_reset(dev, adf41513_close, st);
+ ret = devm_add_action_or_reset(dev, adf41513_close, st);
+ 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 int adf41513_pm_suspend(struct device *dev)
--
2.43.0
^ permalink raw reply related
* [PATCH v12 07/11] iio: frequency: adf41513: driver implementation
From: Rodrigo Alencar via B4 Relay @ 2026-05-10 12:42 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: <20260510-adf41513-iio-driver-v12-0-34af2ed2779f@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 and
reference path configuration;
- Power management support with suspend/resume callbacks;
- Lock detect GPIO monitoring.
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 | 1098 ++++++++++++++++++++++++++++++++++++++
4 files changed, 1110 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 69646ebe5762..b5bf5f7de9c9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1668,6 +1668,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..b8a8b9fb81d9
--- /dev/null
+++ b/drivers/iio/frequency/adf41513.c
@@ -0,0 +1,1098 @@
+// 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 ADF41510_MAX_RF_FREQ_HZ (10ULL * HZ_PER_GHZ)
+#define ADF41513_MIN_RF_FREQ_HZ (1ULL * 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_MIN_CP_CURRENT_uA 81
+#define ADF41513_MAX_CP_CURRENT_uA 7200
+
+#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_SCALE 6
+#define ADF41513_PS_BIAS_INIT 0x2
+#define ADF41513_MAX_PHASE_MICRORAD ((2 * 314159265UL) / 100)
+
+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_val;
+};
+
+struct adf41513_state {
+ const struct adf41513_chip_info *chip_info;
+ struct spi_device *spi;
+ struct gpio_desc *lock_detect;
+ 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;
+
+ bool powerdown;
+};
+
+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;
+
+ /* write registers in reverse order (R13 to R0)*/
+ for (int 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_val = 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]);
+
+ if (!cfg->mod2)
+ cfg->mod2 = 1;
+ if (!cfg->r_counter)
+ cfg->r_counter = ADF41513_MAX_R_CNT;
+
+ /* 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_val * 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)
+{
+ u32 max_int = st->chip_info->has_prescaler_8_9 ?
+ ADF41513_MAX_INT_8_9 : ADF41513_MAX_INT_4_5;
+ u64 freq_error_uhz;
+ u32 int_val = 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_val < max_int) {
+ int_val++;
+ 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_val >= ADF41513_MIN_INT_8_9 &&
+ int_val <= ADF41513_MAX_INT_8_9)
+ result->prescaler = 1;
+ else if (int_val >= ADF41513_MIN_INT_4_5 && int_val <= ADF41513_MAX_INT_4_5)
+ result->prescaler = 0;
+ else
+ return -ERANGE;
+
+ result->actual_frequency_uhz = (u64)int_val * result->pfd_frequency_uhz;
+ result->mode = ADF41513_MODE_INTEGER_N;
+ result->int_val = int_val;
+ 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 resolution_uhz = div_u64(result->pfd_frequency_uhz, ADF41513_FIXED_MODULUS);
+ u64 target_frequency_uhz = result->target_frequency_uhz;
+ u64 freq_error_uhz;
+ u32 int_val, frac1;
+ 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_val = div64_u64_rem(target_frequency_uhz, result->pfd_frequency_uhz,
+ &freq_error_uhz);
+
+ if (st->chip_info->has_prescaler_8_9 && int_val >= ADF41513_MIN_INT_FRAC_8_9 &&
+ int_val <= ADF41513_MAX_INT_8_9)
+ result->prescaler = 1;
+ else if (int_val >= ADF41513_MIN_INT_FRAC_4_5 && int_val <= 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_val * 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_val = int_val;
+ 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;
+ u32 int_val = div64_u64_rem(result->target_frequency_uhz,
+ result->pfd_frequency_uhz, &freq_error_uhz);
+
+ if (st->chip_info->has_prescaler_8_9 && int_val >= ADF41513_MIN_INT_FRAC_8_9 &&
+ int_val <= ADF41513_MAX_INT_8_9)
+ result->prescaler = 1;
+ else if (int_val >= ADF41513_MIN_INT_FRAC_4_5 && int_val <= 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_val * 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_val = int_val;
+ 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)
+ 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)
+ 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) {
+ dev_err(&st->spi->dev,
+ "no valid PLL configuration found for %llu uHz\n",
+ rf_out_uhz);
+ return ret;
+ }
+
+ 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_val, 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_val);
+ 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 % ADF41513_MAX_R_CNT);
+ 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_ALL);
+}
+
+static ssize_t adf41513_read_resolution(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct adf41513_state *st = iio_priv(indio_dev);
+ int vals[2];
+
+ guard(mutex)(&st->lock);
+
+ iio_val_s64_array_populate(st->data.freq_resolution_uhz, vals);
+ return iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(vals), vals);
+}
+
+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);
+
+ val = FIELD_GET(ADF41513_REG6_POWER_DOWN_MSK, st->regs_hw[ADF41513_REG6]);
+ return sysfs_emit(buf, "%u\n", val);
+}
+
+static ssize_t adf41513_write_resolution(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);
+ u64 freq_uhz;
+ int ret;
+
+ ret = kstrtoudec64(buf, ADF41513_HZ_DECIMAL_SCALE, &freq_uhz);
+ if (ret)
+ return ret;
+
+ if (freq_uhz == 0 || freq_uhz > ADF41513_MAX_FREQ_RESOLUTION_UHZ)
+ return -EINVAL;
+
+ guard(mutex)(&st->lock);
+
+ st->data.freq_resolution_uhz = freq_uhz;
+ return 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);
+ bool val;
+ int ret;
+
+ ret = kstrtobool(buf, &val);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&st->lock);
+
+ if (val)
+ ret = adf41513_suspend(st);
+ else
+ ret = adf41513_resume(st);
+ if (ret)
+ return ret;
+
+ st->powerdown = val;
+ return len;
+}
+
+static const struct iio_chan_spec_ext_info adf41513_ext_info[] = {
+ {
+ .name = "frequency_resolution",
+ .read = adf41513_read_resolution,
+ .write = adf41513_write_resolution,
+ .shared = IIO_SEPARATE,
+ },
+ {
+ .name = "powerdown",
+ .read = adf41513_read_powerdown,
+ .write = adf41513_write_powerdown,
+ .shared = IIO_SEPARATE,
+ },
+ { }
+};
+
+static const struct iio_chan_spec adf41513_chan = {
+ .type = IIO_ALTVOLTAGE,
+ .indexed = 1,
+ .output = 1,
+ .channel = 0,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_FREQUENCY) |
+ 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 tmp64;
+
+ guard(mutex)(&st->lock);
+
+ switch (info) {
+ case IIO_CHAN_INFO_FREQUENCY:
+ if (st->lock_detect &&
+ !gpiod_get_value_cansleep(st->lock_detect)) {
+ dev_dbg(&st->spi->dev, "PLL un-locked\n");
+ return -EBUSY;
+ }
+ tmp64 = adf41513_pll_get_rate(st);
+ iio_val_s64_decompose(tmp64, val, val2);
+ return IIO_VAL_DECIMAL64_MICRO;
+ case IIO_CHAN_INFO_PHASE:
+ tmp64 = FIELD_GET(ADF41513_REG2_PHASE_VAL_MSK,
+ st->regs_hw[ADF41513_REG2]);
+ tmp64 = (tmp64 * ADF41513_MAX_PHASE_MICRORAD) >> 12;
+ iio_val_s64_decompose(tmp64, val, val2);
+ return IIO_VAL_DECIMAL64_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 tmp64 = iio_val_s64_compose(val, val2);
+ u16 phase_val;
+
+ guard(mutex)(&st->lock);
+
+ switch (info) {
+ case IIO_CHAN_INFO_FREQUENCY:
+ return adf41513_set_frequency(st, tmp64, ADF41513_SYNC_DIFF);
+ case IIO_CHAN_INFO_PHASE:
+ if (tmp64 >= ADF41513_MAX_PHASE_MICRORAD)
+ return -EINVAL;
+
+ phase_val = DIV_U64_ROUND_CLOSEST(tmp64 << 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_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_FREQUENCY:
+ case IIO_CHAN_INFO_PHASE:
+ return IIO_VAL_DECIMAL64_MICRO;
+ 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,
+ .write_raw_get_fmt = adf41513_write_raw_get_fmt,
+ .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 > st->chip_info->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) {
+ if (cp_current < ADF41513_MIN_CP_CURRENT_uA ||
+ cp_current > ADF41513_MAX_CP_CURRENT_uA)
+ return dev_err_probe(dev, -ERANGE,
+ "I_CP %u uA out of range\n", cp_current);
+
+ tmp = DIV_ROUND_CLOSEST(cp_current * cp_resistance, MILLI);
+ 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 void adf41513_chip_disable(void *data)
+{
+ gpiod_set_value_cansleep(data, 0);
+}
+
+static void adf41513_close(void *data)
+{
+ adf41513_suspend(data);
+}
+
+static int adf41513_setup(struct device *dev, struct adf41513_state *st)
+{
+ u32 tmp;
+ int ret;
+
+ 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 */
+ ret = adf41513_set_frequency(st, st->data.power_up_frequency_hz * MICRO,
+ ADF41513_SYNC_ALL);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dev, adf41513_close, st);
+}
+
+static int adf41513_pm_suspend(struct device *dev)
+{
+ struct adf41513_state *st = dev_get_drvdata(dev);
+
+ guard(mutex)(&st->lock);
+ return adf41513_suspend(st);
+}
+
+static int adf41513_pm_resume(struct device *dev)
+{
+ struct adf41513_state *st = dev_get_drvdata(dev);
+
+ guard(mutex)(&st->lock);
+ if (st->powerdown)
+ return 0; /* nothing to do */
+
+ return adf41513_resume(st);
+}
+
+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 gpio_desc *chip_enable;
+ 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->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");
+
+ chip_enable = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_HIGH);
+ if (IS_ERR(chip_enable))
+ return dev_err_probe(dev, PTR_ERR(chip_enable),
+ "fail to request chip enable GPIO\n");
+
+ ret = devm_add_action_or_reset(dev, adf41513_chip_disable, chip_enable);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add disable action\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(dev, st);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to setup device\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 v12 06/11] iio: test: iio-test-format: add test case for decimal format
From: Rodrigo Alencar via B4 Relay @ 2026-05-10 12:42 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: <20260510-adf41513-iio-driver-v12-0-34af2ed2779f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add iio_test_iio_format_value_decimal_64() kunit test case for decimal
value formatting, exploring different scales types. Also, the same
iio_val_s64_array_populate() macro used to populate local array is used in
iio_test_iio_format_value_integer_64().
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/test/iio-test-format.c | 97 +++++++++++++++++++++++++++++---------
1 file changed, 75 insertions(+), 22 deletions(-)
diff --git a/drivers/iio/test/iio-test-format.c b/drivers/iio/test/iio-test-format.c
index 872dd8582003..a2a9b4360c92 100644
--- a/drivers/iio/test/iio-test-format.c
+++ b/drivers/iio/test/iio-test-format.c
@@ -200,56 +200,108 @@ static void iio_test_iio_format_value_multiple(struct kunit *test)
static void iio_test_iio_format_value_integer_64(struct kunit *test)
{
int values[2];
- s64 value;
char *buf;
int ret;
buf = kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf);
- value = 24;
- values[0] = lower_32_bits(value);
- values[1] = upper_32_bits(value);
+ iio_val_s64_array_populate(24, values);
ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "24\n");
- value = -24;
- values[0] = lower_32_bits(value);
- values[1] = upper_32_bits(value);
+ iio_val_s64_array_populate(-24, values);
ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-24\n");
- value = 0;
- values[0] = lower_32_bits(value);
- values[1] = upper_32_bits(value);
+ iio_val_s64_array_populate(0, values);
ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0\n");
- value = UINT_MAX;
- values[0] = lower_32_bits(value);
- values[1] = upper_32_bits(value);
+ iio_val_s64_array_populate(UINT_MAX, values);
ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "4294967295\n");
- value = -((s64)UINT_MAX);
- values[0] = lower_32_bits(value);
- values[1] = upper_32_bits(value);
+ iio_val_s64_array_populate(-((s64)UINT_MAX), values);
ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-4294967295\n");
- value = LLONG_MAX;
- values[0] = lower_32_bits(value);
- values[1] = upper_32_bits(value);
+ iio_val_s64_array_populate(LLONG_MAX, values);
ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "9223372036854775807\n");
- value = LLONG_MIN;
- values[0] = lower_32_bits(value);
- values[1] = upper_32_bits(value);
+ iio_val_s64_array_populate(LLONG_MIN, values);
ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-9223372036854775808\n");
}
+static void iio_test_iio_format_value_decimal_64(struct kunit *test)
+{
+ int values[2];
+ char *buf;
+ int ret;
+
+ buf = kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf);
+
+ /* DECIMAL64_MILLI: positive >= 1, value 1.234 */
+ iio_val_s64_array_populate(1234, values);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MILLI, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "1.234\n");
+
+ /* DECIMAL64_MICRO: positive >= 1, value 3.141592 */
+ iio_val_s64_array_populate(3141592, values);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "3.141592\n");
+
+ /* DECIMAL64_MILLI: positive < 1, value 0.042 */
+ iio_val_s64_array_populate(42, values);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MILLI, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0.042\n");
+
+ /* DECIMAL64_MILLI: negative <= -1, value -1.234 */
+ iio_val_s64_array_populate(-1234, values);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MILLI, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-1.234\n");
+
+ /* DECIMAL64_MILLI: negative > -1, value -0.123 */
+ iio_val_s64_array_populate(-123, values);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MILLI, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-0.123\n");
+
+ /* DECIMAL64_MILLI: zero */
+ iio_val_s64_array_populate(0, values);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MILLI, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0.000\n");
+
+ /* DECIMAL64_NANO: value 1.000000001 */
+ iio_val_s64_array_populate(1000000001, values);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_NANO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "1.000000001\n");
+
+ /* DECIMAL64_MICRO: large value using upper 32 bits */
+ iio_val_s64_array_populate(5000000000000042LL, values);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "5000000000.000042\n");
+
+ /* limits */
+ iio_val_s64_array_populate(LLONG_MAX, values);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_PICO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "9223372.036854775807\n");
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_NANO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "9223372036.854775807\n");
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "9223372036854.775807\n");
+
+ iio_val_s64_array_populate(LLONG_MIN, values);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_PICO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-9223372.036854775808\n");
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_NANO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-9223372036.854775808\n");
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-9223372036854.775808\n");
+}
+
static struct kunit_case iio_format_test_cases[] = {
KUNIT_CASE(iio_test_iio_format_value_integer),
KUNIT_CASE(iio_test_iio_format_value_fixedpoint),
@@ -257,6 +309,7 @@ static struct kunit_case iio_format_test_cases[] = {
KUNIT_CASE(iio_test_iio_format_value_fractional_log2),
KUNIT_CASE(iio_test_iio_format_value_multiple),
KUNIT_CASE(iio_test_iio_format_value_integer_64),
+ KUNIT_CASE(iio_test_iio_format_value_decimal_64),
{ }
};
--
2.43.0
^ permalink raw reply related
* [PATCH v12 05/11] iio: core: add decimal value formatting into 64-bit value
From: Rodrigo Alencar via B4 Relay @ 2026-05-10 12:42 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: <20260510-adf41513-iio-driver-v12-0-34af2ed2779f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Create new format types for iio values (IIO_VAL_DECIMAL64_*), which
defines the representation of fixed decimal point values into a single
64-bit number. This new format increases the range of represented values,
allowing for integer parts greater than 2^32, as bits are not "wasted"
in the fractional part, which can be seen in IIO_VAL_INT_PLUS_MICRO and
IIO_VAL_INT_PLUS_NANO. Helpers are created to compose and decompose 64-bit
decimals into integer values used in IIO formatting interfaces, which
creates consistency and avoid error-prone manual assignments when using
wordpart macros. When doing the parsing, kstrtodec64() is used with the
scale defined by the specific decimal format type.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/industrialio-core.c | 46 +++++++++++++++++++++++++++++++++--------
include/linux/iio/types.h | 28 +++++++++++++++++++++++++
2 files changed, 65 insertions(+), 9 deletions(-)
diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c
index bd6f4f9f4533..24bc1577fdac 100644
--- a/drivers/iio/industrialio-core.c
+++ b/drivers/iio/industrialio-core.c
@@ -19,6 +19,7 @@
#include <linux/idr.h>
#include <linux/kdev_t.h>
#include <linux/kernel.h>
+#include <linux/math64.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/poll.h>
@@ -26,7 +27,6 @@
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/wait.h>
-#include <linux/wordpart.h>
#include <linux/iio/buffer.h>
#include <linux/iio/buffer_impl.h>
@@ -707,8 +707,25 @@ static ssize_t __iio_format_value(char *buf, size_t offset, unsigned int type,
case IIO_VAL_CHAR:
return sysfs_emit_at(buf, offset, "%c", (char)vals[0]);
case IIO_VAL_INT_64:
- tmp2 = (s64)((((u64)vals[1]) << 32) | (u32)vals[0]);
+ tmp2 = iio_val_s64_from_array(vals);
return sysfs_emit_at(buf, offset, "%lld", tmp2);
+ case IIO_VAL_DECIMAL64_MILLI:
+ case IIO_VAL_DECIMAL64_MICRO:
+ case IIO_VAL_DECIMAL64_NANO:
+ case IIO_VAL_DECIMAL64_PICO:
+ {
+ s64 frac;
+ unsigned int scale = type - IIO_VAL_DECIMAL64_BASE;
+
+ tmp2 = div64_s64_rem(iio_val_s64_from_array(vals),
+ int_pow(10, scale), &frac);
+ if (tmp2 == 0 && frac < 0)
+ return sysfs_emit_at(buf, offset, "-0.%0*lld", scale,
+ abs(frac));
+ else
+ return sysfs_emit_at(buf, offset, "%lld.%0*lld", tmp2,
+ scale, abs(frac));
+ }
default:
return 0;
}
@@ -977,7 +994,7 @@ static ssize_t iio_write_channel_info(struct device *dev,
{
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
- int ret, fract_mult = 100000;
+ int type, ret, fract_mult = 100000, dec_scale = 0;
int integer, fract = 0;
long long integer64;
bool is_char = false;
@@ -988,9 +1005,11 @@ static ssize_t iio_write_channel_info(struct device *dev,
if (!indio_dev->info->write_raw)
return -EINVAL;
- if (indio_dev->info->write_raw_get_fmt)
- switch (indio_dev->info->write_raw_get_fmt(indio_dev,
- this_attr->c, this_attr->address)) {
+ if (indio_dev->info->write_raw_get_fmt) {
+ type = indio_dev->info->write_raw_get_fmt(indio_dev,
+ this_attr->c,
+ this_attr->address);
+ switch (type) {
case IIO_VAL_INT:
fract_mult = 0;
break;
@@ -1006,12 +1025,19 @@ static ssize_t iio_write_channel_info(struct device *dev,
case IIO_VAL_CHAR:
is_char = true;
break;
+ case IIO_VAL_DECIMAL64_MILLI:
+ case IIO_VAL_DECIMAL64_MICRO:
+ case IIO_VAL_DECIMAL64_NANO:
+ case IIO_VAL_DECIMAL64_PICO:
+ dec_scale = type - IIO_VAL_DECIMAL64_BASE;
+ fallthrough;
case IIO_VAL_INT_64:
is_64bit = true;
break;
default:
return -EINVAL;
}
+ }
if (is_char) {
char ch;
@@ -1020,12 +1046,14 @@ static ssize_t iio_write_channel_info(struct device *dev,
return -EINVAL;
integer = ch;
} else if (is_64bit) {
- ret = kstrtoll(buf, 0, &integer64);
+ if (dec_scale)
+ ret = kstrtodec64(buf, dec_scale, &integer64);
+ else
+ ret = kstrtoll(buf, 0, &integer64);
if (ret)
return ret;
- fract = upper_32_bits(integer64);
- integer = lower_32_bits(integer64);
+ iio_val_s64_decompose(integer64, &integer, &fract);
} else {
ret = __iio_str_to_fixpoint(buf, fract_mult, &integer, &fract,
scale_db);
diff --git a/include/linux/iio/types.h b/include/linux/iio/types.h
index 4e3099defc1d..bc0e6f66bd9c 100644
--- a/include/linux/iio/types.h
+++ b/include/linux/iio/types.h
@@ -7,6 +7,7 @@
#ifndef _IIO_TYPES_H_
#define _IIO_TYPES_H_
+#include <linux/wordpart.h>
#include <uapi/linux/iio/types.h>
enum iio_event_info {
@@ -34,6 +35,33 @@ enum iio_event_info {
#define IIO_VAL_FRACTIONAL_LOG2 11
#define IIO_VAL_CHAR 12
+#define IIO_VAL_DECIMAL64_BASE 100
+#define IIO_VAL_DECIMAL64_MILLI (IIO_VAL_DECIMAL64_BASE + 3)
+#define IIO_VAL_DECIMAL64_MICRO (IIO_VAL_DECIMAL64_BASE + 6)
+#define IIO_VAL_DECIMAL64_NANO (IIO_VAL_DECIMAL64_BASE + 9)
+#define IIO_VAL_DECIMAL64_PICO (IIO_VAL_DECIMAL64_BASE + 12)
+
+static inline s64 iio_val_s64_compose(int val0, int val1)
+{
+ return (s64)(((u64)val1 << 32) | (u32)val0);
+}
+
+static inline s64 iio_val_s64_from_array(const int *vals)
+{
+ return iio_val_s64_compose(vals[0], vals[1]);
+}
+
+static inline void iio_val_s64_decompose(s64 dec64, int *val0, int *val1)
+{
+ *val0 = lower_32_bits(dec64);
+ *val1 = upper_32_bits(dec64);
+}
+
+static inline void iio_val_s64_array_populate(s64 dec64, int *vals)
+{
+ iio_val_s64_decompose(dec64, &vals[0], &vals[1]);
+}
+
enum iio_available_type {
IIO_AVAIL_LIST,
IIO_AVAIL_RANGE,
--
2.43.0
^ permalink raw reply related
* [PATCH v12 04/11] lib: math: div64: add div64_s64_rem()
From: Rodrigo Alencar via B4 Relay @ 2026-05-10 12:42 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: <20260510-adf41513-iio-driver-v12-0-34af2ed2779f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add div64_s64_rem() function, with 32-bit implementation that uses
div64_u64_rem() and a branchless approach to resolve the sign of the
remainder and quotient (negation in two's complement).
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
include/linux/math64.h | 18 ++++++++++++++++++
lib/math/div64.c | 15 +++++++++++++++
2 files changed, 33 insertions(+)
diff --git a/include/linux/math64.h b/include/linux/math64.h
index cc305206d89f..99189410d4bb 100644
--- a/include/linux/math64.h
+++ b/include/linux/math64.h
@@ -57,6 +57,20 @@ static inline u64 div64_u64_rem(u64 dividend, u64 divisor, u64 *remainder)
return dividend / divisor;
}
+/**
+ * div64_s64_rem - signed 64bit divide with 64bit divisor and remainder
+ * @dividend: signed 64bit dividend
+ * @divisor: signed 64bit divisor
+ * @remainder: pointer to signed 64bit remainder
+ *
+ * Return: sets ``*remainder``, then returns dividend / divisor
+ */
+static inline s64 div64_s64_rem(s64 dividend, s64 divisor, s64 *remainder)
+{
+ *remainder = dividend % divisor;
+ return dividend / divisor;
+}
+
/**
* div64_u64 - unsigned 64bit divide with 64bit divisor
* @dividend: unsigned 64bit dividend
@@ -102,6 +116,10 @@ extern s64 div_s64_rem(s64 dividend, s32 divisor, s32 *remainder);
extern u64 div64_u64_rem(u64 dividend, u64 divisor, u64 *remainder);
#endif
+#ifndef div64_s64_rem
+extern s64 div64_s64_rem(s64 dividend, s64 divisor, s64 *remainder);
+#endif
+
#ifndef div64_u64
extern u64 div64_u64(u64 dividend, u64 divisor);
#endif
diff --git a/lib/math/div64.c b/lib/math/div64.c
index d1e92ea24fce..0b10ded09a9b 100644
--- a/lib/math/div64.c
+++ b/lib/math/div64.c
@@ -158,6 +158,21 @@ u64 div64_u64(u64 dividend, u64 divisor)
EXPORT_SYMBOL(div64_u64);
#endif
+#ifndef div64_s64_rem
+s64 div64_s64_rem(s64 dividend, s64 divisor, s64 *remainder)
+{
+ s64 quot, t, rem;
+
+ quot = div64_u64_rem(abs(dividend), abs(divisor), (u64 *)&rem);
+ t = dividend >> 63;
+ *remainder = (rem ^ t) - t;
+ t = (dividend ^ divisor) >> 63;
+
+ return (quot ^ t) - t;
+}
+EXPORT_SYMBOL(div64_s64_rem);
+#endif
+
#ifndef div64_s64
s64 div64_s64(s64 dividend, s64 divisor)
{
--
2.43.0
^ permalink raw reply related
* [PATCH v12 02/11] lib: kstrtox: add kstrtoudec64() and kstrtodec64()
From: Rodrigo Alencar via B4 Relay @ 2026-05-10 12:42 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: <20260510-adf41513-iio-driver-v12-0-34af2ed2779f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add helpers that parses decimal numbers into 64-bit number, i.e., decimal
point numbers with pre-defined scale are parsed into a 64-bit value (fixed
precision). After the decimal point, digits beyond the specified scale
are ignored.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
include/linux/kstrtox.h | 3 ++
lib/kstrtox.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 110 insertions(+)
diff --git a/include/linux/kstrtox.h b/include/linux/kstrtox.h
index 6ea897222af1..bec2fc17bde0 100644
--- a/include/linux/kstrtox.h
+++ b/include/linux/kstrtox.h
@@ -97,6 +97,9 @@ int __must_check kstrtou8(const char *s, unsigned int base, u8 *res);
int __must_check kstrtos8(const char *s, unsigned int base, s8 *res);
int __must_check kstrtobool(const char *s, bool *res);
+int __must_check kstrtoudec64(const char *s, unsigned int scale, u64 *res);
+int __must_check kstrtodec64(const char *s, unsigned int scale, s64 *res);
+
int __must_check kstrtoull_from_user(const char __user *s, size_t count, unsigned int base, unsigned long long *res);
int __must_check kstrtoll_from_user(const char __user *s, size_t count, unsigned int base, long long *res);
int __must_check kstrtoul_from_user(const char __user *s, size_t count, unsigned int base, unsigned long *res);
diff --git a/lib/kstrtox.c b/lib/kstrtox.c
index 97be2a39f537..da7b5f83a3c5 100644
--- a/lib/kstrtox.c
+++ b/lib/kstrtox.c
@@ -17,6 +17,7 @@
#include <linux/export.h>
#include <linux/kstrtox.h>
#include <linux/math64.h>
+#include <linux/overflow.h>
#include <linux/types.h>
#include <linux/uaccess.h>
@@ -392,6 +393,112 @@ int kstrtobool(const char *s, bool *res)
}
EXPORT_SYMBOL(kstrtobool);
+static int _kstrtoudec64(const char *s, unsigned int scale, u64 *res)
+{
+ u64 _res = 0, _frac = 0;
+ unsigned int rv;
+
+ if (scale > 19) /* log10(2^64) = 19.26 */
+ return -EINVAL;
+
+ if (*s != '.') {
+ rv = _parse_integer(s, 10, &_res);
+ if (rv & KSTRTOX_OVERFLOW)
+ return -ERANGE;
+ if (rv == 0)
+ return -EINVAL;
+ s += rv;
+ }
+
+ if (*s == '.' && scale) {
+ s++; /* skip decimal point */
+ rv = _parse_integer_limit(s, 10, &_frac, scale);
+ if (rv & KSTRTOX_OVERFLOW)
+ return -ERANGE;
+ if (rv == 0)
+ return -EINVAL;
+ s += rv;
+ if (rv < scale)
+ _frac *= int_pow(10, scale - rv);
+ while (isdigit(*s)) /* truncate */
+ s++;
+ }
+
+ if (*s == '\n')
+ s++;
+ if (*s)
+ return -EINVAL;
+
+ if (check_mul_overflow(_res, int_pow(10, scale), &_res) ||
+ check_add_overflow(_res, _frac, &_res))
+ return -ERANGE;
+
+ *res = _res;
+ return 0;
+}
+
+/**
+ * kstrtoudec64() - Convert a string to an unsigned 64-bit value that represents
+ * a scaled decimal number.
+ * @s: The start of the string. The string must be null-terminated, and may also
+ * include a single newline before its terminating null. The first character
+ * may also be a plus sign, but not a minus sign. Digits beyond the specified
+ * scale are ignored.
+ * @scale: The number of digits to the right of the decimal point. For example,
+ * a scale of 2 would mean the number is represented with two decimal places,
+ * so "123.45" would be represented as 12345.
+ * @res: Where to write the result of the conversion on success.
+ *
+ * Return: 0 on success, -ERANGE on overflow and -EINVAL on parsing error.
+ */
+noinline
+int kstrtoudec64(const char *s, unsigned int scale, u64 *res)
+{
+ if (s[0] == '+')
+ s++;
+ return _kstrtoudec64(s, scale, res);
+}
+EXPORT_SYMBOL(kstrtoudec64);
+
+/**
+ * kstrtodec64() - Convert a string to a signed 64-bit value that represents a
+ * scaled decimal number.
+ * @s: The start of the string. The string must be null-terminated, and may also
+ * include a single newline before its terminating null. The first character
+ * may also be a plus sign or a minus sign. Digits beyond the specified
+ * scale are ignored.
+ * @scale: The number of digits to the right of the decimal point. For example,
+ * a scale of 5 would mean the number is represented with five decimal places,
+ * so "-3.141592" would be represented as -314159.
+ * @res: Where to write the result of the conversion on success.
+ *
+ * Return: 0 on success, -ERANGE on overflow and -EINVAL on parsing error.
+ */
+noinline
+int kstrtodec64(const char *s, unsigned int scale, s64 *res)
+{
+ u64 tmp;
+ int rv;
+
+ if (s[0] == '-') {
+ rv = _kstrtoudec64(s + 1, scale, &tmp);
+ if (rv < 0)
+ return rv;
+ if ((s64)-tmp > 0)
+ return -ERANGE;
+ *res = -tmp;
+ } else {
+ rv = kstrtoudec64(s, scale, &tmp);
+ if (rv < 0)
+ return rv;
+ if ((s64)tmp < 0)
+ return -ERANGE;
+ *res = tmp;
+ }
+ return 0;
+}
+EXPORT_SYMBOL(kstrtodec64);
+
/*
* Since "base" would be a nonsense argument, this open-codes the
* _from_user helper instead of using the helper macro below.
--
2.43.0
^ permalink raw reply related
* [PATCH v12 03/11] lib: test-kstrtox: tests for kstrtodec64() and kstrtoudec64()
From: Rodrigo Alencar via B4 Relay @ 2026-05-10 12:42 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: <20260510-adf41513-iio-driver-v12-0-34af2ed2779f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add tests for decimal parsing helpers kstrtodec64() and kstrtoudec64().
The test infrastructure is reused from other kstrto*() functions, i.e.,
the decimal parsers have fixed base of 10, so base field is used as
scale input for the helpers.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
lib/test-kstrtox.c | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 156 insertions(+)
diff --git a/lib/test-kstrtox.c b/lib/test-kstrtox.c
index ee87fef66cb5..ee9b535bcf1c 100644
--- a/lib/test-kstrtox.c
+++ b/lib/test-kstrtox.c
@@ -703,6 +703,156 @@ static void __init test_kstrtos8_fail(void)
TEST_FAIL(kstrtos8, s8, "%hhd", test_s8_fail);
}
+static void __init test_kstrtoudec64_ok(void)
+{
+ DECLARE_TEST_OK(u64, struct test_udec64);
+ static DEFINE_TEST_OK(struct test_udec64, test_udec64_ok) = {
+ /* basic: integer.fraction, exact digits */
+ {"0.0", 1, 0},
+ {"1.5", 1, 15},
+ {"1.234", 3, 1234},
+ {"42.0", 1, 420},
+ /* zero */
+ {"0.0", 1, 0},
+ {"0.000", 3, 0},
+ /* integer only (no decimal point) */
+ {"0", 1, 0},
+ {"42", 3, 42000},
+ {"1", 1, 10},
+ /* fractional only (leading dot) */
+ {".5", 1, 5},
+ {".123", 3, 123},
+ {".001", 3, 1},
+ /* zero padding: fewer fractional digits than scale */
+ {"1.2", 3, 1200},
+ {"1.2", 6, 1200000},
+ {"0.01", 3, 10},
+ {"0.1", 9, 100000000ULL},
+ {"0.01", 9, 10000000},
+ /* truncation: more fractional digits than scale */
+ {"1.23456", 3, 1234},
+ {"3.1415926535", 6, 3141592},
+ {"0.999999999", 3, 999},
+ {"1.99", 1, 19},
+ /* trailing newline */
+ {"1.5\n", 1, 15},
+ {"42\n", 3, 42000},
+ /* plus sign */
+ {"+1.5", 1, 15},
+ {"+.5", 1, 5},
+ /* scale progression */
+ {"1.0", 1, 10},
+ {"1.00", 2, 100},
+ {"1.000", 3, 1000},
+ {"1.000000", 6, 1000000},
+ {"1.000000000", 9, 1000000000ULL},
+ /* large values spanning u64 range */
+ {"9223372036.854775807", 9, 9223372036854775807ULL},
+ {"18446744073709.551615", 6, 18446744073709551615ULL},
+ };
+ TEST_OK(kstrtoudec64, u64, "%llu", test_udec64_ok);
+}
+
+static void __init test_kstrtoudec64_fail(void)
+{
+ static DEFINE_TEST_FAIL(test_udec64_fail) = {
+ /* empty / whitespace */
+ {"", 3},
+ {"\n", 3},
+ /* invalid scale */
+ {"1.0", 21},
+ /* minus sign (unsigned) */
+ {"-1.5", 1},
+ {"-0.5", 1},
+ /* no digits after dot */
+ {"1.", 3},
+ {".", 3},
+ /* no digits at all */
+ {"+", 3},
+ /* non-digit characters */
+ {"abc", 3},
+ {"1.2x", 3},
+ /* leading/trailing space */
+ {" 1.5", 1},
+ {"1.5 ", 1},
+ /* overflow */
+ {"18446744073710.551615", 6},
+ {"99999999999999999999", 1},
+ };
+ TEST_FAIL(kstrtoudec64, u64, "%llu", test_udec64_fail);
+}
+
+static void __init test_kstrtodec64_ok(void)
+{
+ DECLARE_TEST_OK(s64, struct test_dec64);
+ static DEFINE_TEST_OK(struct test_dec64, test_dec64_ok) = {
+ /* basic positive */
+ {"0.0", 1, 0},
+ {"1.5", 1, 15},
+ {"1.234", 3, 1234},
+ /* basic negative */
+ {"-1.5", 1, -15},
+ {"-1.234", 3, -1234},
+ {"-0.5", 1, -5},
+ {"-0.001", 3, -1},
+ /* zero (signed) */
+ {"-0", 1, 0},
+ {"-0.0", 1, 0},
+ {"0.000", 3, 0},
+ /* integer only */
+ {"42", 3, 42000},
+ {"-42", 3, -42000},
+ /* fractional only */
+ {".5", 1, 5},
+ {"-.5", 1, -5},
+ /* zero padding */
+ {"1.2", 3, 1200},
+ {"-1.2", 3, -1200},
+ {"0.01", 3, 10},
+ {"-0.01", 3, -10},
+ /* truncation */
+ {"1.23456", 3, 1234},
+ {"-1.23456", 3, -1234},
+ {"0.999999999", 3, 999},
+ {"-0.999999999", 3, -999},
+ /* trailing newline */
+ {"1.5\n", 1, 15},
+ {"-1.5\n", 1, -15},
+ /* plus sign */
+ {"+1.5", 1, 15},
+ /* limits */
+ {"9223372036.854775807", 9, LLONG_MAX},
+ {"-9223372036.854775808", 9, LLONG_MIN},
+ };
+ TEST_OK(kstrtodec64, s64, "%lld", test_dec64_ok);
+}
+
+static void __init test_kstrtodec64_fail(void)
+{
+ static DEFINE_TEST_FAIL(test_dec64_fail) = {
+ /* empty / whitespace */
+ {"", 3},
+ {"\n", 3},
+ /* invalid scale */
+ {"1.0", 21},
+ /* no digits after dot */
+ {"1.", 3},
+ {".", 3},
+ {"-.", 3},
+ /* no digits at all */
+ {"+", 3},
+ {"-", 3},
+ /* non-digit characters */
+ {"abc", 3},
+ {"-1.2x", 3},
+ /* signed overflow */
+ {"9223372036.854775808", 9},
+ {"-9223372036.854775809", 9},
+ {"99999999999999999999", 1},
+ };
+ TEST_FAIL(kstrtodec64, s64, "%lld", test_dec64_fail);
+}
+
static int __init test_kstrtox_init(void)
{
test_kstrtoull_ok();
@@ -729,6 +879,12 @@ static int __init test_kstrtox_init(void)
test_kstrtou8_fail();
test_kstrtos8_ok();
test_kstrtos8_fail();
+
+ test_kstrtoudec64_ok();
+ test_kstrtoudec64_fail();
+ test_kstrtodec64_ok();
+ test_kstrtodec64_fail();
+
return -EINVAL;
}
module_init(test_kstrtox_init);
--
2.43.0
^ permalink raw reply related
* [PATCH v12 01/11] dt-bindings: iio: frequency: add adf41513
From: Rodrigo Alencar via B4 Relay @ 2026-05-10 12:42 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: <20260510-adf41513-iio-driver-v12-0-34af2ed2779f@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 | 227 +++++++++++++++++++++
MAINTAINERS | 7 +
2 files changed, 234 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..f4fae9210382
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/frequency/adi,adf41513.yaml
@@ -0,0 +1,227 @@
+# 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
+
+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:
+ minimum: 81
+ maximum: 7200
+ 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
+
+allOf:
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: adi,adf41510
+ then:
+ properties:
+ adi,power-up-frequency-mhz:
+ maximum: 10000
+
+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 bc42e1cacd6b..69646ebe5762 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1662,6 +1662,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 v12 00/11] ADF41513/ADF41510 PLL frequency synthesizers
From: Rodrigo Alencar via B4 Relay @ 2026-05-10 12:42 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
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 v12:
- Contraint charge pump current.
- Fix division-by-zero issues.
- Address PM and sysfs powerdown conflicts.
- Program proper phase resync value in clk divider mode.
- Link to v11: https://lore.kernel.org/r/20260506-adf41513-iio-driver-v11-0-2b7e99cfe8f2@analog.com
Changes in v11:
- Cleanup ext info attribute read/write callbacks.
- Adjust attribute names in the documentation.
- Turn s64 compose macros into static inline functions.
- Link to v10: https://lore.kernel.org/r/20260415-adf41513-iio-driver-v10-0-df61046d5457@analog.com
Changes in v10:
- Drop simple_strntoull() changes
- Create kstrtodec64() and kstrtoudec64() helpers.
- Add IIO value format for 64-bit decimal values.
- PLL driver code implements new decimal format for frequency attr.
- Link to v9: https://lore.kernel.org/r/20260320-adf41513-iio-driver-v9-0-132f0d076374@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 (11):
dt-bindings: iio: frequency: add adf41513
lib: kstrtox: add kstrtoudec64() and kstrtodec64()
lib: test-kstrtox: tests for kstrtodec64() and kstrtoudec64()
lib: math: div64: add div64_s64_rem()
iio: core: add decimal value formatting into 64-bit value
iio: test: iio-test-format: add test case for decimal format
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 | 227 ++++
Documentation/iio/adf41513.rst | 199 ++++
Documentation/iio/index.rst | 1 +
MAINTAINERS | 9 +
drivers/iio/frequency/Kconfig | 10 +
drivers/iio/frequency/Makefile | 1 +
drivers/iio/frequency/adf41513.c | 1231 ++++++++++++++++++++
drivers/iio/industrialio-core.c | 46 +-
drivers/iio/test/iio-test-format.c | 97 +-
include/linux/iio/types.h | 28 +
include/linux/kstrtox.h | 3 +
include/linux/math64.h | 18 +
lib/kstrtox.c | 107 ++
lib/math/div64.c | 15 +
lib/test-kstrtox.c | 156 +++
17 files changed, 2128 insertions(+), 41 deletions(-)
---
base-commit: 39b80c5c9830d12d2d6531059001301c4265322a
change-id: 20251110-adf41513-iio-driver-aaca8a7f808e
Best regards,
--
Rodrigo Alencar <rodrigo.alencar@analog.com>
^ permalink raw reply
* Re: [RFC net-next 0/4] devlink: Add boot-time defaults
From: Mark Bloch @ 2026-05-10 12:31 UTC (permalink / raw)
To: Jiri Pirko, Jakub Kicinski
Cc: Eric Dumazet, Paolo Abeni, Andrew Lunn, David S. Miller,
Jonathan Corbet, Shuah Khan, Simon Horman, Saeed Mahameed,
Leon Romanovsky, Tariq Toukan, Andrew Morton,
Borislav Petkov (AMD), Randy Dunlap, Dave Hansen,
Christian Brauner, Petr Mladek, Peter Zijlstra (Intel),
Thomas Gleixner, Pawan Gupta, Dapeng Mi, Kees Cook, Marco Elver,
Eric Biggers, Li RongQing, Paul E. McKenney, linux-doc,
linux-kernel, netdev, linux-rdma
In-Reply-To: <af7Y4AYv-XDCbK_8@FV6GYCPJ69>
On 09/05/2026 10:01, Jiri Pirko wrote:
> Sat, May 09, 2026 at 02:52:13AM +0200, kuba@kernel.org wrote:
>> On Fri, 8 May 2026 20:07:44 +0200 Jiri Pirko wrote:
>>>> I don't think switchdev by default should mean CX4+ in general. If we get
>>>> there, I would expect it to be limited to the DPU/BlueField/ECPF case, where
>>>> the host PF probe path can depend on the ECPF reaching switchdev. Changing the
>>>> default for regular host NIC deployments feels like a much larger compatibility
>>>> change.
>>>
>>> We can't travel throught time, but if from CX5 onwards the default would
>>> be switchdev, nobody would feel broken in terms of compatibility. That
>>> is my point. Having "legacy" as default is simply wrong for never NIC
>>> generations. That is why it is called "legacy" and it should have been
>>> rotten through and out since CX4 times.
>>
>> legacy vs switchdev only describes the eswitch configuration.
>> As a non-SR-IOV user I really don't want to see the extra representors
>> hanging around my systems, confusing all daemons. IIRC mlx5 had some
>> limitations around the uplink representor. Maybe that's the disconnect.
>> But for a real, fully featured switchdev eswitches having the
>> PHY and PF representors on boot, always, will not make sense.
>
> As "a non-SR-IOV user", what extra representors you talk about? When you
> have pfs only, you don't have anything extra. Just 1 netdev per-pf, one
> devlink port per-pf. What's extra about it? When you don't have VFs/SFs.
> Everyhing is the same:
The netdev list looking similar is a bit misleading. What matters here is
not only how many netdevs show up, but what that netdev actually is.
In legacy mode, a PF only user can just use the PF netdev as a regular NIC
and use ROCE on it directly.
In switchdev mode, even if there are no VFs or SFs yet, the PF is moved into
the switchdev model and the visible netdev is the uplink representor. That is
not the same thing from a user point of view. The uplink representor is not a
ROCE capable endpoint. So a user who used to boot the machine and use ROCE on
the PF now has to create a VF or SF, use that as the roce endpoint, and also
set up the switchdev forwarding path with tc, bridge or OVS so traffic from
that function actually reaches the wire.
That is why I don't think this is only a card generation question. It changes
the deployment model. It may be the right default for BlueField/ECPF style
systems, where the host is expected to sit behind a switchdev control plane,
but it is not a safe default for every regular host NIC setup.
>
> c-220-136-220-218:~$ sudo devlink dev eswitch show pci/0000:08:00.0
> pci/0000:08:00.0: mode switchdev inline-mode none encap-mode basic
> c-220-136-220-218:~$ sudo devlink dev eswitch show pci/0000:08:00.1
> pci/0000:08:00.1: mode legacy inline-mode none encap-mode basic
> c-220-136-220-218:~$ devlink dev
> pci/0000:08:00.0: index 0
> nested_devlink:
> auxiliary/mlx5_core.eth.0
> devlink_index/1: index 1
> nested_devlink:
> pci/0000:08:00.0
> pci/0000:08:00.1
> auxiliary/mlx5_core.eth.0: index 2
> pci/0000:08:00.1: index 3
> nested_devlink:
> auxiliary/mlx5_core.eth.1
> auxiliary/mlx5_core.eth.1: index 4
> c-220-136-220-218:~$ devlink port
> auxiliary/mlx5_core.eth.0/65535: type eth netdev eth2 flavour physical port 0 splittable false
> auxiliary/mlx5_core.eth.1/131071: type eth netdev eth3 flavour physical port 1 splittable false
> c-220-136-220-218:~$ ip link
> ...
> 4: eth2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
> link/ether b8:e9:24:f2:b7:6c brd ff:ff:ff:ff:ff:ff
> altname enp8s0f0np0
> 5: eth3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
> link/ether b8:e9:24:f2:b7:6d brd ff:ff:ff:ff:ff:ff
> altname enp8s0f1np1
>
>
>>
>> IOW it's not a question of the generation of the card but of
>> the deployment type / use case.
>
> I don't think so, not in the case of mlx5. The difference is only when
> you work with sr-iov, you either use legacy way (ip vf) or the new one.
> Same usecase.
>
>
>>
>>>> For the ASIC/NV bit: maybe technically possible, but it feels like the wrong
>>>> layer. This is boot/deployment policy, not a persistent hardware property, and
>>>> storing it in NV memory would make the state persist across kernels/hosts in a
>>>> surprising way.
>>>
>>> Well, as any other nv config, it persists across kernels/hosts. Think
>>> about it as "unbreak-my-not-legacy-device" bit.
>>
>> For most devices the switchdev mode does not change anything
>> substantial about the device. It's purely a kernel / driver config.
>> It changes what objects and default rules kernel / driver installs.
>> So I don't get why it would make sense to flash into the device
>> nvmem a Linux SW stack specific config.
>
> I look at it from the perspective that from some CX generation,
> switchdev mode should be default. So that is a device-based decision.
> I believe as such it can optionally be permanenty configured (nv config)
> on older device. Why not?
This is a deployment policy decision, not a permanent property of the card.
The same adapter can be used in a regular host/RDMA setup or in a
switchdev/offload setup. If we store this in NVM, that Linux switchdev policy
follows the device across hosts, kernels and use cases, and can surprise the
next deployment that just expects a normal NIC.
I'll send another RFC v2 with support limited to:
devlink=[...]:esw:mode:{ switchdev | switchdev_inactive | legacy }
and let's see where we land with that.
I still think a small kernel command line knob is the cleanest way to get to
"switchdev by default" without making the interface too broad. For more
complex boot-time configuration, I agree that a devlinkd or similar userspace
path is probably the better direction.
The "pause probing until userspace configures devlink" idea feels less clear
to me. It is not quite the simple boot policy knob, and not quite the full
userspace policy manager either. It would add a new probe state and require
early userspace orchestration before the device is fully materialized. At
least for now, I would prefer either the small cmdline option for the simple
global/default case, or a proper devlinkd-like solution for more complex
policy. Between those, I still prefer the cmdline option for this specific
early eswitch mode default.
Mark
>
> [...]
^ permalink raw reply
* Re: [PATCH v2 0/7] seg6: add SRv6 Mobile User Plane (RFC 9433) behaviors
From: Yuya Kusakabe @ 2026-05-10 12:02 UTC (permalink / raw)
To: Andrea Mayer
Cc: Jakub Kicinski, David S. Miller, Eric Dumazet, Paolo Abeni,
Simon Horman, Shuah Khan, Jonathan Corbet, Shuah Khan,
linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
linux-kselftest@vger.kernel.org, linux-doc@vger.kernel.org,
Justin Iurman, stefano.salsano
In-Reply-To: <CAGCJULP_dTSjXQqyOYXckkmtd-HAPo4UT2V0WQofOZaJYLgENw@mail.gmail.com>
On Fri, May 8, 2026 at 10:32 AM Andrea Mayer <andrea.mayer@uniroma2.it> wrote:
> just a heads-up: I am going through the series (kernel and iproute2)
> and will send detailed comments within the next few days. It is a
> substantial addition so I want to take the time to review it properly.
Quick note from my side before you spend more time on patch 4/7:
While preparing v3 I noticed that v2's patch 4/7 ("End.M.GTP6.D")
implements RFC 9433 Section 6.4 (D.Di) by mistake -- it shares the
SRH-augmenting builder with patch 5/7 and preserves the original
outer DA in segments[0]. Per Section 6.3 it should push the SR
Policy verbatim and write Args.Mob.Session into segments[0].
Already fixed in the local v3 branch.
Feel free to skip the End.M.GTP6.D-specific comments on v2 patch
4/7; patch 5/7 (D.Di) is unaffected. Per Jakub's guidance I will
hold the v3 repost until your review lands.
Thanks,
Yuya
^ permalink raw reply
* Re: [PATCH RFC v4 02/10] iio: core: support 64-bit register through debugfs
From: Andy Shevchenko @ 2026-05-10 10:07 UTC (permalink / raw)
To: rodrigo.alencar
Cc: linux-iio, devicetree, linux-kernel, linux-doc, linux-hardening,
Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
Kees Cook, Gustavo A. R. Silva
In-Reply-To: <20260508-ad9910-iio-driver-v4-2-d26bfd20ee3d@analog.com>
On Fri, May 08, 2026 at 06:00:18PM +0100, Rodrigo Alencar via B4 Relay wrote:
> Add debugfs_reg64_access function pointer field into iio_info and modify
> file operation callbacks to favor 64-bit variant when it is available.
To write a value with bit 63 set it will require negative number, right?
Isn't this counter intuitive and may lead to rejection of the (correct)
values?
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [RFC v2 0/2] add kconfirm
From: Miguel Ojeda @ 2026-05-10 9:49 UTC (permalink / raw)
To: Jan Engelhardt
Cc: Julian Braha, nathan, nsc, jani.nikula, akpm, gary, ljs, arnd,
gregkh, masahiroy, ojeda, corbet, qingfang.deng, linux-kernel,
rust-for-linux, linux-doc, linux-kbuild
In-Reply-To: <q02rn6o5-5pr6-1744-6os9-1052roro79s8@vanv.qr>
On Sun, May 10, 2026 at 7:06 AM Jan Engelhardt <ej@inai.de> wrote:
>
> Good lord, how is anyone supposed to review that amount –
> or is it just getting rubberstamped anyway?
Yeah, if one really wants to statically vendor the files, then please
follow the pattern we used for other vendoring: see the commits that
introduced e.g. `rust/syn/`.
In particular, we try to cut down there the dependencies and the files
within each dependency to those that are actually needed: no need to
support vendor optional dependencies that aren't used (and if they are
used, please try to see if they could be avoided), no need to support
all platforms (e.g. why do we need FreeBSD files here?), no need to
vendor the tests nor scripts, and so on.
For instance, for `syn`, I modified it (minimally) to cut down one
dependency. I also provided a script in the commit message to verify
the files are 1:1 identical to the ones upstream (before adapting them
with SPDX identifiers etc.).
So, from a quick look, here I see files like:
scripts/kconfirm/vendor/vcpkg/test-data/normalized/installed/x86-windows/bin/freetype.dll
Which I would be surprised if they are needed.
In any case, when we discussed offline building in v1, that did not
necessarily mean vendoring every dependency manually into the tree,
but rather let the user set up the dependencies before (i.e.
connecting is fine) so that then the actual `make` steps can proceed
offline. For instance, using `cargo vendor`:
https://doc.rust-lang.org/cargo/commands/cargo-vendor.html
In other words, one should be able to have users run a command or
similar, and then use the dependencies that are already downloaded.
By the way, another option for that may be using the distribution's
registry (e.g. Debian and Fedora provide one through the package
manager). That is even better (and we were requested to look into it
back then for `syn`), but it does introduce complications even if one
assumes Cargo is available (which we don't so far in the normal build
path), e.g. the versions need to fit, one still needs to provide a way
to do it for distributions that do not match, etc.
I hope that helps!
Cheers,
Miguel
^ permalink raw reply
* Re: [PATCH RFC v4 10/10] docs: iio: add documentation for ad9910 driver
From: Rodrigo Alencar @ 2026-05-10 9:30 UTC (permalink / raw)
To: David Lechner, rodrigo.alencar, linux-iio, devicetree,
linux-kernel, linux-doc, linux-hardening
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Jonathan Corbet, Shuah Khan, Kees Cook,
Gustavo A. R. Silva
In-Reply-To: <b8f9a174-f3d0-4cb8-a571-605be79165d6@baylibre.com>
On 26/05/09 06:42PM, David Lechner wrote:
> On 5/8/26 12:00 PM, Rodrigo Alencar via B4 Relay wrote:
> > From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> >
> > Add documentation for the AD9910 DDS IIO driver, which describes channels,
> > DDS modes, attributes and ABI usage examples.
...
> > +Channel hierarchy
> > +=================
> > +
> > +The driver exposes the following IIO output channels, each identified by a
> > +unique channel number and a human-readable label:
> > +
>
> Can we format this as a table with a header to make it clear what each item is?
> I'm guessing that the second `` is the label?
>
> And perhaps provide a link to the sections below that describe the common attributes
> of each channel type?
Yes, that is a label. A table is better indeed.
> > +* ``out_altvoltage100``: ``phy``: Physical output: system clock and profile control
>
> Any attributes on this one?
>
> > +
> > + * ``out_altvoltage101``: ``profile[0]``: Single tone control for profile 0:
>
> Why not just ``profile0``?
>
> Also, why not ``out_altvoltage110`` so that the last digit matches the profile
> index? It looks like we are skipping by 10s later anyway.
Yeah, that can be done. I thought of out_altvoltage110 being a channel to hold common
things between profiles, but it ended up empty so I left the spot as a placeholder.
> > + frequency, phase, amplitude
> > +
> > + * ``out_altvoltage102``: ``profile[1]``: Single tone control for profile 1:
> > + frequency, phase, amplitude
> > +
> > + * ``out_altvoltage103``: ``profile[2]``: Single tone control for profile 2:
> > + frequency, phase, amplitude
> > +
> > + * ``out_altvoltage104``: ``profile[3]``: Single tone control for profile 3:
> > + frequency, phase, amplitude
> > +
> > + * ``out_altvoltage105``: ``profile[4]``: Single tone control for profile 4:
> > + frequency, phase, amplitude
> > +
> > + * ``out_altvoltage106``: ``profile[5]``: Single tone control for profile 5:
> > + frequency, phase, amplitude
> > +
> > + * ``out_altvoltage107``: ``profile[6]``: Single tone control for profile 6:
> > + frequency, phase, amplitude
> > +
> > + * ``out_altvoltage108``: ``profile[7]``: Single tone control for profile 7:
> > + frequency, phase, amplitude
> > +
> > + * ``out_altvoltage110``: ``parallel_port``: Parallel port modulation channel
>
> I guess no attributes on this one yet since implementation is deferred?
Only basic knobs will be exposed, proper implementation will come with a later backend
support.
> > +
> > + * ``out_altvoltage120``: ``digital_ramp_generator``: DRG control: enable
> > +
> > + * ``out_altvoltage121``: ``digital_ramp_up``: DRG ramp-up parameters:
> > + dwell enable, limits, rate of change, ramp rate
> > + * ``out_altvoltage122``: ``digital_ramp_down``: DRG ramp-down parameters:
> > + dwell enable, limits, rate of change, ramp rate
>
> Oh, I guess these are just the general "control knob" name, not the actual
> sysfs attribute name.
Correct, just a description.
>
> > +
> > + * ``out_altvoltage130``: ``ram_control``: RAM playback: enable, frequency,
> > + phase and sampling frequency for active profile. Other configurations are
> > + provided through a firmware upload interface.
> > +
> > + * ``out_altvoltage150``: ``output_shift_keying``: OSK: enable, amplitude
> > + scale, ramp rate, rate of change control
> > +
> > +The ``phy`` channel is the root of the hierarchy. Changing its
> > +``sampling_frequency`` reconfigures the system clock (SYSCLK) which affects all
> > +other channels.
> > +
> > +Most of the mode-specific channels (single-tone, DRG, RAM, OSK) have an
> > +``enable`` attribute that turns the mode on/off.
> > +
...
> > +Parallel Port mode
> > +------------------
> > +
> > +The parallel port allows real-time modulation of DDS parameters through a
> > +16-bit external data bus.
> > +
> > +.. flat-table::
> > + :header-rows: 1
> > +
> > + * - Attribute
> > + - Unit
> > + - Description
> > +
> > + * - ``frequency_scale``
> > + - power-of-2
> > + - FM gain multiplier applied to 16-bit parallel input. Range :math:`[1, 32768]`,
>
> General comment for the whole doc. Can you spell out the acronyms the
> first time they are used for us noobs.
>
> > + must be a power of 2.
> > +
> > + * - ``frequency_offset``
> > + - Hz
> > + - Base FTW to which scaled parallel data is added. Range :math:`[0, f_{SYSCLK}/2)`.
> > +
> > + * - ``phase_offset``
> > + - rad
> > + - Base phase for polar modulation. Lower 8 bits of POW register.
> > + Range :math:`[0, 2\pi/256)`.
> > +
> > + * - ``scale_offset``
> > + - fractional
> > + - Base amplitude for polar modulation. Lower 6 bits of ASF register.
> > + Range :math:`[0, 1/256)`.
> > +
>
> I guess there was some discussion on these attributes. I see some of these in the
> ad9832 driver in staging, but I'm guessing they are new ABI. It isn't clear to
> me from the documentation here what they actually do though. I guess they are
> just basic transformations on the input signal?
Not sure how the ABI is not clear:
For a channel that allows amplitude control through buffers, this
represents the value for a base amplitude scale. The actual output
amplitude scale is a result with the sum of this value.
So yes, it is a basic transformation.
>
> And a practical note, they should be "frequencyscale". I don't like that it is
> harder to read, but it is easier for a machine to parse.
Parsers like the ones in libiio is not having problems with that.
> > +Usage examples
> > +^^^^^^^^^^^^^^
> > +
> > +Set parallel port frequency modulation with a scale of 16 and a 50 MHz
> > +offset:
> > +
> > +.. code-block:: bash
> > +
> > + echo 16 > /sys/bus/iio/devices/iio:device0/out_altvoltage113_frequency_scale
> > + echo 50000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage113_frequency_offset
> > +
> > +Digital ramp generator (DRG)
> > +----------------------------
> > +
> > +The DRG produces linear frequency, phase or amplitude sweeps using dedicated
> > +hardware. It is controlled through three channels: a parent control channel
> > +(``digital_ramp_generator``) and two child ramp channels
> > +(``digital_ramp_up``, ``digital_ramp_down``). DRG destination is set when
> > +ramp attributes are written, i.e. writing to ``frequency`` or ``frequency_roc``
> > +sets the destination to frequency.
>
> Would it be better to say that the destination is set when the the
> value is non-zero? Otherwise, how would one change the destination
> once set?
Destination is only one, so you just need to write phase or phase_roc, if you want
to target phase then. Does that not sound intuitive?
Zero is a valid value to be written.
>
> > +
> > +Control channel attributes
> > +^^^^^^^^^^^^^^^^^^^^^^^^^^
> > +
> > +.. flat-table::
> > + :header-rows: 1
> > +
> > + * - Attribute
> > + - Unit
> > + - Description
> > +
> > + * - ``en``
> > + - boolean
> > + - Enable/disable the DRG.
> > +
> > +Ramp channel attributes
> > +^^^^^^^^^^^^^^^^^^^^^^^^
> > +
> > +The ``digital_ramp_up`` and ``digital_ramp_down`` channels share the same
> > +attribute set but configure ascending and descending ramp parameters
> > +independently:
> > +
> > +.. flat-table::
> > + :header-rows: 1
> > +
> > + * - Attribute
> > + - Unit
> > + - Description
> > +
> > + * - ``dwell_en``
> > + - boolean
> > + - Enable dwell at the ramp limit. When disabled, the ramp auto-transitions
> > + at this limit without waiting for the DRCTL pin. Disabling both creates a
> > + bidirectional continuous ramp (Triangular pattern). Other configurations
> > + create a single-shot ramp at the transition of the DRCTL pin: ramp-up
> > + only, ramp-down only or bidirectional with dwell at the limits.
> > +
> > + * - ``frequency``
> > + - Hz
> > + - Frequency ramp limit. Range: :math:`[0, f_{SYSCLK}/2)`. Writing a value
> > + sets the ramp destination to frequency. Reading back returns the
> > + currently active frequency limit or -EBUSY if other destination is
> > + active (phase or amplitude).
> > +
> > + * - ``phase``
> > + - rad
> > + - Phase ramp limit. Range: :math:`[0, 2\pi)`. Writing a value sets the
> > + ramp destination to phase. Reading back returns the currently active
> > + phase limit or -EBUSY if other destination is active (frequency or
> > + amplitude).
> > +
> > + * - ``scale``
> > + - fractional
> > + - Amplitude scale ramp limit. Range: :math:`[0, 1)`. Writing a value sets
> > + the ramp destination to amplitude. Reading back returns the currently
> > + active scale limit or -EBUSY if other destination is active (frequency
> > + or phase).
> > +
> > + * - ``sampling_frequency``
> > + - Hz
> > + - Ramp clock rate. It is controlled by an integer divider so the requested
> > + value will adjust to nearest supported value.
> > +
> > + * - ``frequency_roc``
> > + - Hz/s
> > + - Frequency rate of change. Sets the per-tick frequency increment/decrement
> > + based on the current ramp clock rate.
> > +
> > + * - ``phase_roc``
> > + - rad/s
> > + - Phase rate of change. Sets the per-tick phase increment/decrement based
> > + on the current ramp clock rate.
> > +
> > + * - ``scale_roc``
> > + - 1/s
> > + - Amplitude scale rate of change. Sets the per-tick amplitude scale
> > + increment/decrement based on the current ramp clock rate.
> > +
> > +Usage examples
> > +^^^^^^^^^^^^^^
> > +
> > +Configure a frequency sweep from 40 MHz to 60 MHz with a rate of change of
> > +25 GHz/s:
> > +
> > +.. code-block:: bash
> > +
> > + # Disable dwell on both limits for a bidirectional continuous ramp
> > + echo 0 > /sys/bus/iio/devices/iio:device0/out_altvoltage121_dwell_en
> > + echo 0 > /sys/bus/iio/devices/iio:device0/out_altvoltage122_dwell_en
> > +
> > + # Set ramp limits
> > + echo 60000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage121_frequency
> > + echo 40000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage122_frequency
> > +
> > + # Set ramp rate
> > + echo 25000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage121_sampling_frequency
> > + echo 25000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage122_sampling_frequency
> > +
> > + # Set frequency rate of change (Hz/s)
> > + echo 25000000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage121_frequency_roc
> > + echo 25000000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage122_frequency_roc
> > +
> > + # Enable the DRG
> > + echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage120_en
> > +
> > +RAM mode
> > +--------
> > +
> > +The AD9910 contains a 1024 x 32-bit RAM that can be loaded with waveform data
> > +and played back to modulate frequency, phase, amplitude, or polar (phase +
> > +amplitude) parameters.
> > +
> > +RAM control channel attributes
> > +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> > +
> > +.. flat-table::
> > + :header-rows: 1
> > +
> > + * - Attribute
> > + - Unit
> > + - Description
> > +
> > + * - ``en``
> > + - boolean
> > + - Enable/disable RAM playback. Toggling swaps profile registers between
> > + single tone and RAM configurations across all 8 profiles.
> > +
> > + * - ``frequency``
> > + - Hz
> > + - Frequency tuning word used as the single tone frequency when
> > + RAM destination is not ``frequency``. Range: :math:`[0, f_{SYSCLK}/2)`.
> > +
> > + * - ``phase``
> > + - rad
> > + - Phase offset word used as the single tone phase when RAM destination
> > + is not ``phase``. Range: :math:`[0, 2\pi)`.
> > +
> > + * - ``sampling_frequency``
> > + - Hz
> > + - RAM playback step rate of the active profile, which controls how fast the
> > + address counter advances. It is controlled by an integer divider so the
> > + requested value will adjust to nearest supported value.
> > +
> > +Loading RAM data
> > +^^^^^^^^^^^^^^^^
> > +
> > +RAM data is loaded through the firmware upload framework. The driver registers
> > +a firmware upload sysfs entry named ``iio_deviceX:ram``. The FW data follows
> > +a simple binary format:
> > +
> > +- 80-byte header:
> > +
> > + - 4-byte big-endian magic word: 0x00AD9910;
> > + - 4-byte big-endian CFR1 value: configuration for the CFR1 register. Only
> > + bits relevant to RAM mode (data destination and internal profile control)
> > + are considered. Other bits are ignored and have no effect:
> > +
> > + - Bits [30:29]: RAM data destination:
> > +
> > + - 00: frequency;
> > + - 01: phase;
> > + - 10: amplitude;
> > + - 11: polar;
> > +
> > + - Bits [20:17]: Internal profile control (see Table 14 of the datasheet);
> > +
> > + - 8 sets of 8-byte big-endian profile data for profiles 0-7. Each set contains:
> > +
> > + - Bits [55:40]: Address step rate value;
> > + - Bits [39:30]: End address for the profile;
> > + - Bits [23:14]: Start address for the profile;
> > + - Bit [5]: no-dwell high for ramp-up mode;
> > + - Bit [3]: zero-crossing for direct-switch mode;
> > + - Bits [2:0]: operating mode:
> > +
> > + - 000: direct switch;
> > + - 001: ramp-up;
> > + - 010: bidirectional;
> > + - 011: bidirectional continuous;
> > + - 100: ramp-up continuous;
> > +
> > + - 4-byte big-endian reserved word: set to 0;
>
> Will it be enough? :-)
>
> Another option could be to include a file format version field.
Yeah, maybe a CRC and a version as you pointed out. In terms of RAM functionality,
that would be all. Maybe a table for this one too...
> > + - 4-byte big-endian word count: number of 32-bit words to be loaded (0-1024);
> > +
> > +- Followed by the specified number of 32-bit big-endian data words.
> > +
> > +Usage examples
> > +^^^^^^^^^^^^^^
> > +
> > +Configure RAM mode with firmware data and enable it:
> > +
> > +.. code-block:: bash
> > +
> > + # Load RAM data via firmware upload
> > + echo 1 > /sys/class/firmware/iio\:device0\:ram/loading
> > + cat ad9910-ram.bin > /sys/class/firmware/iio\:device0\:ram/data
> > + echo 0 > /sys/class/firmware/iio\:device0\:ram/loading
> > +
> > + # Enable RAM mode
> > + echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage130_en
> > +
...
> > +Physical channel
> > +================
> > +
> > +The ``phy`` channel provides device-level control:
> > +
> > +.. flat-table::
> > + :header-rows: 1
> > +
> > + * - Attribute
> > + - Unit
> > + - Description
> > +
> > + * - ``sampling_frequency``
> > + - Hz
> > + - System clock (SYSCLK) frequency. With PLL enabled, configures the PLL
> > + multiplier (range 420-1000 MHz). Without PLL, ref clock can only be
> > + divided by 2.
>
> What controls the PLL?
It gets enabled in the device-tree. One would want that when feeding a lower clock rate
source as a reference clock. It would also need a loop-filter connected to the device.
This property can be used to configure the desired sysclk frequency, the PLL divider/multiplier
and VCO configs will be derived from that.
> > +
> > + * - ``powerdown``
> > + - boolean (0 or 1)
> > + - Software power-down. Writing 1 powers down the digital core, DAC,
> > + reference clock input and auxiliary DAC simultaneously.
> > +
...
>
> I like the direction this is going. Looks sensible to me.
>
> I didn't have time to read the code, so just going off of the docs for now.
Thanks for the review. The code would need some cleanup after the ABI is mature.
Also, sashiko is pointing out a lot of issues already.. so those I suppose I can
handle on my own for now.
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply
* Re: [PATCH RFC v4 00/10] AD9910 Direct Digital Synthesizer
From: Rodrigo Alencar @ 2026-05-10 8:50 UTC (permalink / raw)
To: David Lechner, rodrigo.alencar, linux-iio, devicetree,
linux-kernel, linux-doc, linux-hardening
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Jonathan Corbet, Shuah Khan, Kees Cook,
Gustavo A. R. Silva
In-Reply-To: <f3bb9f64-a0ef-4862-afdd-74ee39d7bfc1@baylibre.com>
On 26/05/09 05:31PM, David Lechner wrote:
> On 5/8/26 12:00 PM, Rodrigo Alencar via B4 Relay wrote:
> > This patch series adds support for the Analog Devices AD9910 DDS.
> > This is a RFC so that we can agree/discuss on the design that follows:
> >
> > This is a follow-up of the V3 discussion. For V1, we reached into
> > this channel composition agreement where physical channels may have
> > sub-channels. That adds the flexibility necessary for this design.
> > During V2, some feedback indicated that the ABI is too device-specific,
> > so DRG/RAM destination and operating modes are configured through
> > alternate paths and profile channels are created. In V3, there was
> > further discussion on the ABI and on mode priority debug.
> >
> What happened with the idea of adding a new attribute to show the
> relationship of the sub-channels to the actual physical output
> channels?
That's still to be done in iio core. I was still to think on how to do that,
and I am trying to get a mature ABI first.
I am not sure about the use case where a sub-channel is shared between
multiple channels, but I thought of a iio_chan_spec pointer to a parent
iio_chan_spec in the same struct. Similar to a device-tree, we have the
primary tree structure and then phandles can be used separately to create
more complex dependencies between channels. So a "channel ref" attribute
could be separate concept.
Then iio core would create the read-only attribute "subcomponent_of" or
"parent" when that is not NULL. The read function would just output the parent
channel label. Then labels would be important to create this logical dependency
between channels, and maybe that is bad, but in this context, I suppose labels
are going to be needed anyways.
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply
* [PATCH] docs/zh_CN: update admin-guide/index.rst translation
From: Yan Zhu @ 2026-05-10 6:48 UTC (permalink / raw)
To: corbet, alexs, si.yanteng, kees
Cc: skhan, dzm91, tony.luck, gpiccoli, frederic, jani.nikula, longman,
mchehab+huawei, linux-doc, linux-kernel, Yan Zhu
update Documentation/admin-guide/index.rst Chinese translation
Update the translation through commit f0efd29aa60c
("doc: Add CPU Isolation documentation")
Signed-off-by: Yan Zhu <zhuyan2015@qq.com>
---
.../translations/zh_CN/admin-guide/index.rst | 209 +++++++++++++-----
1 file changed, 159 insertions(+), 50 deletions(-)
diff --git a/Documentation/translations/zh_CN/admin-guide/index.rst b/Documentation/translations/zh_CN/admin-guide/index.rst
index 15d9ab5993a7..575449b91916 100644
--- a/Documentation/translations/zh_CN/admin-guide/index.rst
+++ b/Documentation/translations/zh_CN/admin-guide/index.rst
@@ -1,7 +1,13 @@
+.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
-:Original: :doc:`../../../admin-guide/index`
-:Translator: Alex Shi <alex.shi@linux.alibaba.com>
+:Original: Documentation/admin-guide/index.rst
+
+:翻译:
+
+ 时奎亮 Alex Shi <alex.shi@linux.alibaba.com>
+
+ 朱岩 Yan Zhu <zhuyan2015@qq.com>
Linux 内核用户和管理员指南
@@ -11,7 +17,11 @@ Linux 内核用户和管理员指南
整体的顺序或组织 - 这些材料不是一个单一的,连贯的文件!幸运的话,情况会随着
时间的推移而迅速改善。
-这个初始部分包含总体信息,包括描述内核的README, 关于内核参数的文档等。
+
+内核管理通用指南
+----------------
+
+本节包含总体信息,包括描述内核整体的 README 文件、内核参数文档等。
.. toctree::
:maxdepth: 1
@@ -20,17 +30,55 @@ Linux 内核用户和管理员指南
Todolist:
-* kernel-parameters
* devices
+* features
+
+内核管理接口的重要组成部分是 /proc 和 sysfs 虚拟文件系统;这些文档描述了如何
+与之交互。
+
+.. toctree::
+ :maxdepth: 1
+
+ cputopology
+
+
+Todolist:
+* sysfs-rules
* sysctl/index
+* abi
+
+安全相关文档:
+
+.. toctree::
+ :maxdepth: 1
-本节介绍CPU漏洞及其缓解措施。
Todolist:
* hw-vuln/index
+* LSM/index
+* perf-security
+
+
+内核启动
+--------
+
+.. toctree::
+ :maxdepth: 1
+
+ bootconfig
+
+Todolist:
+
+* kernel-parameters
+* efi-stub
+* initrd
+
+
+追踪和识别问题
+--------------
-下面的一组文档,针对的是试图跟踪问题和bug的用户。
+以下是一组面向试图追踪特定问题和 bug 的用户的文档。
.. toctree::
:maxdepth: 1
@@ -39,94 +87,155 @@ Todolist:
reporting-regressions
bug-hunting
bug-bisect
- tainted-kernels
init
+ clearing-warn-once
+ lockup-watchdogs
+ sysrq
Todolist:
+* quickly-build-trimmed-linux
+* verify-bugs-and-bisect-regressions
+* tainted-kernels
* ramoops
* dynamic-debug-howto
* kdump/index
* perf/index
+* pstore-blk
+* kernel-per-CPU-kthreads
+* RAS/index
+
+
+核心内核子系统
+--------------
+
+这些文档描述了核心内核管理接口,这些接口几乎在任何系统上都值得关注。
+
+.. toctree::
+ :maxdepth: 1
+
+ cpu-load
+ mm/index
+ module-signing
+ numastat
-这是应用程序开发人员感兴趣的章节的开始。可以在这里找到涵盖内核ABI各个
-方面的文档。
Todolist:
-* sysfs-rules
+* cgroup-v2
+* cgroup-v1/index
+* namespaces/index
+* pm/index
+* syscall-user-dispatch
-本手册的其余部分包括各种指南,介绍如何根据您的喜好配置内核的特定行为。
+对非原生二进制格式的支持。请注意,其中一些文档相当古老。
+
+.. toctree::
+ :maxdepth: 1
+
+
+Todolist:
+
+* binfmt-misc
+* java
+* mono
+
+
+块设备和文件系统管理
+--------------------
.. toctree::
:maxdepth: 1
- bootconfig
- clearing-warn-once
- cpu-load
- cputopology
- lockup-watchdogs
- numastat
- unicode
- sysrq
- mm/index
Todolist:
-* acpi/index
-* aoe/index
-* auxdisplay/index
* bcache
* binderfs
-* binfmt-misc
* blockdev/index
-* braille-console
-* btmrvl
-* cgroup-v1/index
-* cgroup-v2
* cifs/index
-* dell_rbu
* device-mapper/index
-* edid
-* efi-stub
* ext4
+* filesystem-monitoring
* nfs/index
-* gpio/index
-* highuid
-* hw_random
-* initrd
* iostats
-* java
* jfs
-* kernel-per-CPU-kthreads
+* md
+* ufs
+* xfs
+
+
+专用设备指南
+------------
+
+如何在 Linux 系统中配置硬件。
+
+.. toctree::
+ :maxdepth: 1
+
+
+Todolist:
+
+* acpi/index
+* aoe/index
+* auxdisplay/index
+* braille-console
+* btmrvl
+* dell_rbu
+* edid
+* gpio/index
+* hw_random
* laptops/index
* lcd-panel-cgram
-* ldm
-* LSM/index
-* md
* media/index
-* module-signing
-* mono
-* namespaces/index
+* nvme-multipath
* parport
-* perf-security
-* pm/index
* pnp
* rapidio
-* ras
* rtc
* serial-console
* svga
+* thermal/index
* thunderbolt
-* ufs
* vga-softcursor
* video-output
-* xfs
+
+
+工作负载分析
+------------
+
+这是一个章节的开始,其中包含对从事 Linux 内核安全关键性分析的应用程序开发人员
+和系统集成商感兴趣的信息。这里可以找到支持分析内核与应用程序交互以及关键内核
+子系统预期的文档。
+
+.. toctree::
+ :maxdepth: 1
+
+
+Todolist:
+
+* workload-tracing
+
+
+其他内容
+--------
+
+一些难以分类且通常已过时的文档。
+
+.. toctree::
+ :maxdepth: 1
+
+
+Todolist:
+
+* highuid
+* ldm
+* unicode
.. only:: subproject and html
- Indices
- =======
+ 索引
+ ====
* :ref:`genindex`
--
2.43.0
^ permalink raw reply related
* Re: [RFC v2 0/2] add kconfirm
From: Jan Engelhardt @ 2026-05-10 5:06 UTC (permalink / raw)
To: Julian Braha
Cc: nathan, nsc, jani.nikula, akpm, gary, ljs, arnd, gregkh,
masahiroy, ojeda, corbet, qingfang.deng, linux-kernel,
rust-for-linux, linux-doc, linux-kbuild
In-Reply-To: <20260509203808.1142311-1-julianbraha@gmail.com>
On Saturday 2026-05-09 22:38, Julian Braha wrote:
>
>kconfirm is a tool to detect misusage of Kconfig.
> 3334 files changed, 973634 insertions(+), 2 deletions(-)
Good lord, how is anyone supposed to review that amount –
or is it just getting rubberstamped anyway?
^ permalink raw reply
* [PATCH 4/4] HID: hid-msi-claw: Add Rumble Intensity Attributes
From: Derek J. Clark @ 2026-05-10 4:35 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Pierre-Loup A . Griffais, Denis Benato, Zhouwang Huang,
Derek J . Clark, linux-input, linux-doc, linux-kernel
In-Reply-To: <20260510043510.442807-1-derekjohn.clark@gmail.com>
Adds intensity adjustment for the left and right rumble motors.
Claude was used during the reverse-engineering data gathering for this
feature done by Zhouwang Huang. As the code had already been affected,
I used Claude to create the initial framing for the feature, then did
manual cleanup of the _show and _store functions afterwards to fix bugs
and keep the coding style consistent. Claude was also used as an initial
reviewer of this patch.
Assisted-by: Claude:claude-sonnet-4-6
Co-developed-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
drivers/hid/hid-msi-claw.c | 139 +++++++++++++++++++++++++++++++++++++
1 file changed, 139 insertions(+)
diff --git a/drivers/hid/hid-msi-claw.c b/drivers/hid/hid-msi-claw.c
index f4fe74a784c2..6d089f49abdb 100644
--- a/drivers/hid/hid-msi-claw.c
+++ b/drivers/hid/hid-msi-claw.c
@@ -76,6 +76,8 @@ enum claw_profile_ack_pending {
CLAW_M1_PENDING,
CLAW_M2_PENDING,
CLAW_RGB_PENDING,
+ CLAW_RUMBLE_LEFT_PENDING,
+ CLAW_RUMBLE_RIGHT_PENDING,
};
enum claw_key_index {
@@ -262,6 +264,11 @@ static const u16 button_mapping_addr_new[] = {
static const u16 rgb_addr_old = 0x01fa;
static const u16 rgb_addr_new = 0x024a;
+static const u16 rumble_addr[] = {
+ 0x0022, /* left */
+ 0x0023, /* right */
+};
+
struct claw_command_report {
u8 report_id;
u8 padding[2];
@@ -308,7 +315,10 @@ struct claw_drvdata {
enum claw_gamepad_mode_index gamepad_mode;
u8 m1_codes[CLAW_KEYS_MAX];
u8 m2_codes[CLAW_KEYS_MAX];
+ u8 rumble_intensity_right;
+ u8 rumble_intensity_left;
const u16 *bmap_addr;
+ bool rumble_support;
bool bmap_support;
/* RGB Variables */
@@ -396,6 +406,12 @@ static int claw_profile_event(struct claw_drvdata *drvdata, struct claw_command_
memcpy(&drvdata->rgb_frames[f_idx], &frame->zone_data,
sizeof(struct rgb_frame));
+ break;
+ case CLAW_RUMBLE_LEFT_PENDING:
+ drvdata->rumble_intensity_left = cmd_rep->data[4];
+ break;
+ case CLAW_RUMBLE_RIGHT_PENDING:
+ drvdata->rumble_intensity_right = cmd_rep->data[4];
break;
default:
dev_warn(&drvdata->hdev->dev,
@@ -795,6 +811,116 @@ static ssize_t button_mapping_options_show(struct device *dev,
}
static DEVICE_ATTR_RO(button_mapping_options);
+static ssize_t rumble_intensity_left_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u8 data[] = { 0x01, (rumble_addr[0] >> 8) & 0xff, rumble_addr[0] & 0xff, 0x01, 0x00 };
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ u8 val;
+ int ret;
+
+ ret = kstrtou8(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ if (val > 100)
+ return -EINVAL;
+
+ data[4] = val;
+
+ ret = mcu_property_out(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA,
+ data, ARRAY_SIZE(data), 8);
+ if (ret)
+ return ret;
+
+ ret = mcu_property_out(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 0);
+ if (ret)
+ return ret;
+
+ drvdata->rumble_intensity_left = val;
+
+ return count;
+}
+
+static ssize_t rumble_intensity_left_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u8 data[4] = { 0x01, (rumble_addr[0] >> 8) & 0xff, rumble_addr[0] & 0xff, 0x01 };
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ int ret;
+
+ drvdata->profile_pending = CLAW_RUMBLE_LEFT_PENDING;
+ ret = mcu_property_out(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, data, ARRAY_SIZE(data), 8);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%u\n", drvdata->rumble_intensity_left);
+}
+static DEVICE_ATTR_RW(rumble_intensity_left);
+
+static ssize_t rumble_intensity_right_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u8 data[] = { 0x01, (rumble_addr[1] >> 8) & 0xff, rumble_addr[1] & 0xff, 0x01, 0x00 };
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ u8 val;
+ int ret;
+
+ ret = kstrtou8(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ if (val > 100)
+ return -EINVAL;
+
+ data[4] = val;
+
+ ret = mcu_property_out(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA,
+ data, ARRAY_SIZE(data), 8);
+ if (ret)
+ return ret;
+
+ ret = mcu_property_out(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 0);
+ if (ret)
+ return ret;
+
+ drvdata->rumble_intensity_right = val;
+
+ return count;
+}
+
+static ssize_t rumble_intensity_right_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u8 data[4] = { 0x01, (rumble_addr[1] >> 8) & 0xff, rumble_addr[1] & 0xff, 0x01 };
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ int ret;
+
+ drvdata->profile_pending = CLAW_RUMBLE_RIGHT_PENDING;
+ ret = mcu_property_out(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, data, ARRAY_SIZE(data), 8);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%u\n", drvdata->rumble_intensity_right);
+}
+static DEVICE_ATTR_RW(rumble_intensity_right);
+
+static ssize_t rumble_intensity_range_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "0-100\n");
+}
+static DEVICE_ATTR_RO(rumble_intensity_range);
+
static umode_t claw_gamepad_attr_is_visible(struct kobject *kobj, struct attribute *attr,
int n)
{
@@ -815,6 +941,12 @@ static umode_t claw_gamepad_attr_is_visible(struct kobject *kobj, struct attribu
attr == &dev_attr_reset.attr)
return attr->mode;
+ /* Hide rumble attrs if not supported */
+ if (attr == &dev_attr_rumble_intensity_left.attr ||
+ attr == &dev_attr_rumble_intensity_right.attr ||
+ attr == &dev_attr_rumble_intensity_range.attr)
+ return drvdata->rumble_support ? attr->mode : 0;
+
/* Hide button mapping attrs if it isn't supported */
return drvdata->bmap_support ? attr->mode : 0;
}
@@ -828,6 +960,9 @@ static struct attribute *claw_gamepad_attrs[] = {
&dev_attr_mkeys_function.attr,
&dev_attr_mkeys_function_index.attr,
&dev_attr_reset.attr,
+ &dev_attr_rumble_intensity_left.attr,
+ &dev_attr_rumble_intensity_right.attr,
+ &dev_attr_rumble_intensity_range.attr,
NULL,
};
@@ -1286,9 +1421,11 @@ static void claw_features_supported(struct claw_drvdata *drvdata)
drvdata->bmap_support = true;
if (minor >= 0x66) {
drvdata->bmap_addr = button_mapping_addr_new;
+ drvdata->rumble_support = true;
drvdata->rgb_addr = rgb_addr_new;
} else {
drvdata->bmap_addr = button_mapping_addr_old;
+ drvdata->rumble_support = false;
drvdata->rgb_addr = rgb_addr_old;
}
return;
@@ -1297,11 +1434,13 @@ static void claw_features_supported(struct claw_drvdata *drvdata)
if ((major == 0x02 && minor >= 0x17) || major >= 0x03) {
drvdata->bmap_support = true;
drvdata->bmap_addr = button_mapping_addr_new;
+ drvdata->rumble_support = true;
drvdata->rgb_addr = rgb_addr_new;
return;
}
drvdata->bmap_support = false;
+ drvdata->rumble_support = false;
drvdata->rgb_addr = rgb_addr_old;
}
--
2.53.0
^ permalink raw reply related
* [PATCH 3/4] HID: hid-msi-claw: Add RGB control interface
From: Derek J. Clark @ 2026-05-10 4:35 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Pierre-Loup A . Griffais, Denis Benato, Zhouwang Huang,
Derek J . Clark, linux-input, linux-doc, linux-kernel
In-Reply-To: <20260510043510.442807-1-derekjohn.clark@gmail.com>
Adds RGB control interface for MSI Claw devices. The MSI Claw uses a
fairly unique RGB interface. It has 9 total zones (4 per joystick ring
and 1 for the ABXY buttons), and supports up to 8 sequential frames of
RGB zone data. Each frame is written to a specific area of MCU memory by
the profile command, the value of which changes based on the firmware of
the device. Unlike other devices (such as the Legion Go or the OneXPlayer
devices), there are no hard coded effects built into the MCU. Instead,
the basic effects are provided as a series of frame data. I have
mirrored the effects available in Windows in this driver, while keeping
the effect names consistent with the Lenovo drivers for the effects that
are similar.
Initial reverse-engineering and implementation of this feature was done
by Zhouwang Huang. I refactored the overall format to conform to kernel
driver best practices and style guides. Claude was used as an initial
reviewer of this patch.
Assisted-by: Claude:claude-sonnet-4-6
Co-developed-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
drivers/hid/hid-msi-claw.c | 533 ++++++++++++++++++++++++++++++++++++-
1 file changed, 530 insertions(+), 3 deletions(-)
diff --git a/drivers/hid/hid-msi-claw.c b/drivers/hid/hid-msi-claw.c
index 60694d075d56..f4fe74a784c2 100644
--- a/drivers/hid/hid-msi-claw.c
+++ b/drivers/hid/hid-msi-claw.c
@@ -21,6 +21,7 @@
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/kobject.h>
+#include <linux/led-class-multicolor.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/mutex.h>
@@ -42,6 +43,10 @@
#define CLAW_KEYS_MAX 5
+#define CLAW_RGB_ZONES 9
+#define CLAW_RGB_MAX_FRAMES 8
+#define CLAW_RGB_FRAME_OFFSET 0x24
+
enum claw_command_index {
CLAW_COMMAND_TYPE_READ_PROFILE = 0x04,
CLAW_COMMAND_TYPE_READ_PROFILE_ACK = 0x05,
@@ -70,6 +75,7 @@ enum claw_profile_ack_pending {
CLAW_NO_PENDING,
CLAW_M1_PENDING,
CLAW_M2_PENDING,
+ CLAW_RGB_PENDING,
};
enum claw_key_index {
@@ -227,6 +233,22 @@ static const struct {
{ 0xce, "REL_WHEEL_DOWN" },
};
+enum claw_rgb_effect_index {
+ CLAW_RGB_EFFECT_MONOCOLOR,
+ CLAW_RGB_EFFECT_BREATHE,
+ CLAW_RGB_EFFECT_CHROMA,
+ CLAW_RGB_EFFECT_RAINBOW,
+ CLAW_RGB_EFFECT_FROSTFIRE,
+};
+
+static const char * const claw_rgb_effect_text[] = {
+ [CLAW_RGB_EFFECT_MONOCOLOR] = "monocolor",
+ [CLAW_RGB_EFFECT_BREATHE] = "breathe",
+ [CLAW_RGB_EFFECT_CHROMA] = "chroma",
+ [CLAW_RGB_EFFECT_RAINBOW] = "rainbow",
+ [CLAW_RGB_EFFECT_FROSTFIRE] = "frostfire",
+};
+
static const u16 button_mapping_addr_old[] = {
0x007a, /* M1 */
0x011f, /* M2 */
@@ -237,6 +259,9 @@ static const u16 button_mapping_addr_new[] = {
0x0164, /* M2 */
};
+static const u16 rgb_addr_old = 0x01fa;
+static const u16 rgb_addr_new = 0x024a;
+
struct claw_command_report {
u8 report_id;
u8 padding[2];
@@ -245,6 +270,28 @@ struct claw_command_report {
u8 data[59];
} __packed;
+struct rgb_zone {
+ u8 red;
+ u8 green;
+ u8 blue;
+};
+
+struct rgb_frame {
+ struct rgb_zone zone[9];
+};
+
+struct rgb_report {
+ u8 profile;
+ __be16 read_addr;
+ u8 frame_bytes;
+ u8 padding;
+ u8 frame_count;
+ u8 state;
+ u8 speed;
+ u8 brightness;
+ struct rgb_frame zone_data;
+} __packed;
+
struct claw_drvdata {
/* MCU General Variables */
enum claw_profile_ack_pending profile_pending;
@@ -263,6 +310,16 @@ struct claw_drvdata {
u8 m2_codes[CLAW_KEYS_MAX];
const u16 *bmap_addr;
bool bmap_support;
+
+ /* RGB Variables */
+ struct rgb_frame rgb_frames[CLAW_RGB_MAX_FRAMES];
+ enum claw_rgb_effect_index rgb_effect;
+ struct led_classdev_mc led_mc;
+ struct delayed_work rgb_queue;
+ u8 rgb_frame_count;
+ bool rgb_enabled;
+ u8 rgb_speed;
+ u16 rgb_addr;
};
static int get_endpoint_address(struct hid_device *hdev)
@@ -292,7 +349,10 @@ static int claw_gamepad_mode_event(struct claw_drvdata *drvdata,
static int claw_profile_event(struct claw_drvdata *drvdata, struct claw_command_report *cmd_rep)
{
- u8 *codes;
+ struct rgb_report *frame;
+ u16 rgb_addr, read_addr;
+ u8 *codes, f_idx;
+ u16 frame_calc;
int i;
switch (drvdata->profile_pending) {
@@ -304,6 +364,39 @@ static int claw_profile_event(struct claw_drvdata *drvdata, struct claw_command_
for (i = 0; i < CLAW_KEYS_MAX; i++)
codes[i] = (cmd_rep->data[6 + i] != 0xff) ? cmd_rep->data[6 + i] : 0x00;
break;
+ case CLAW_RGB_PENDING:
+ frame = (struct rgb_report *)cmd_rep->data;
+ rgb_addr = drvdata->rgb_addr;
+ read_addr = be16_to_cpu(frame->read_addr);
+ frame_calc = (read_addr - rgb_addr) / CLAW_RGB_FRAME_OFFSET;
+ if (frame_calc > U8_MAX) {
+ dev_err(drvdata->led_mc.led_cdev.dev, "Got unsupported frame index: %x\n",
+ frame_calc);
+ return -EINVAL;
+ }
+ f_idx = frame_calc;
+
+ if (f_idx >= CLAW_RGB_MAX_FRAMES) {
+ dev_err(drvdata->led_mc.led_cdev.dev, "Got illegal frame index: %x\n",
+ f_idx);
+ return -EINVAL;
+ }
+
+ /* Always treat the first frame as the truth for these constants */
+ if (f_idx == 0) {
+ drvdata->rgb_frame_count = frame->frame_count;
+ /* Invert device speed (20-0) to sysfs speed (0-20) */
+ drvdata->rgb_speed = frame->speed;
+ drvdata->led_mc.led_cdev.brightness = frame->brightness;
+ drvdata->led_mc.subled_info[0].intensity = frame->zone_data.zone[0].red;
+ drvdata->led_mc.subled_info[1].intensity = frame->zone_data.zone[0].green;
+ drvdata->led_mc.subled_info[2].intensity = frame->zone_data.zone[0].blue;
+ }
+
+ memcpy(&drvdata->rgb_frames[f_idx], &frame->zone_data,
+ sizeof(struct rgb_frame));
+
+ break;
default:
dev_warn(&drvdata->hdev->dev,
"Got profile event without changes pending from command:%x\n",
@@ -743,6 +836,389 @@ static const struct attribute_group claw_gamepad_attr_group = {
.is_visible = claw_gamepad_attr_is_visible,
};
+/* Read RGB config from device */
+static int claw_read_rgb_config(struct hid_device *hdev)
+{
+ u8 data[4] = { 0x01, 0x00, 0x00, CLAW_RGB_FRAME_OFFSET };
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ u16 read_addr = drvdata->rgb_addr;
+ size_t len = ARRAY_SIZE(data);
+ int ret, i;
+
+ if (!drvdata->rgb_addr)
+ return -ENODEV;
+
+ /* Loop through all 8 pages of RGB data */
+ for (i = 0; i < 8; i++) {
+ drvdata->profile_pending = CLAW_RGB_PENDING;
+ data[1] = (read_addr >> 8) & 0xff;
+ data[2] = read_addr & 0x00ff;
+ ret = mcu_property_out(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, data, len, 8);
+ if (ret)
+ return ret;
+
+ read_addr += CLAW_RGB_FRAME_OFFSET;
+ }
+
+ return 0;
+}
+
+/* Send RGB configuration to device */
+static int claw_write_rgb_state(struct claw_drvdata *drvdata)
+{
+ struct rgb_report report = { 0x01, 0x0000, CLAW_RGB_FRAME_OFFSET, 0x00,
+ drvdata->rgb_frame_count, 0x09, drvdata->rgb_speed,
+ drvdata->led_mc.led_cdev.brightness };
+ u16 write_addr = drvdata->rgb_addr;
+ size_t len = sizeof(report);
+ int f, ret;
+
+ if (!drvdata->rgb_addr)
+ return -ENODEV;
+
+ /* Loop through (up to) 8 pages of RGB data */
+ for (f = 0; f < drvdata->rgb_frame_count; f++) {
+ report.zone_data = drvdata->rgb_frames[f];
+
+ /* Set the MCU address to write the frame data to */
+ report.read_addr = cpu_to_be16(write_addr);
+
+ /* Serialize the rgb_report and write it to MCU */
+ ret = mcu_property_out(drvdata->hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA,
+ (u8 *)&report, len, 8);
+ if (ret)
+ return ret;
+
+ /* Increment the write addr by the offset for the next frame */
+ write_addr += CLAW_RGB_FRAME_OFFSET;
+ }
+
+ return 0;
+}
+
+/* Fill all zones with the same color */
+static void claw_frame_fill_solid(struct rgb_frame *frame, struct rgb_zone zone)
+{
+ int z;
+
+ for (z = 0; z < CLAW_RGB_ZONES; z++)
+ frame->zone[z] = zone;
+}
+
+/* Apply solid effect (1 frame, all zones same color) */
+static int claw_apply_monocolor(struct claw_drvdata *drvdata)
+{
+ struct mc_subled *subleds = drvdata->led_mc.subled_info;
+ struct rgb_zone zone = { subleds[0].intensity, subleds[1].intensity,
+ subleds[2].intensity };
+
+ drvdata->rgb_frame_count = 1;
+ claw_frame_fill_solid(&drvdata->rgb_frames[0], zone);
+
+ return claw_write_rgb_state(drvdata);
+}
+
+/* Apply breathe effect (2 frames: color -> off) */
+static int claw_apply_breathe(struct claw_drvdata *drvdata)
+{
+ struct mc_subled *subleds = drvdata->led_mc.subled_info;
+ struct rgb_zone zone = { subleds[0].intensity, subleds[1].intensity,
+ subleds[2].intensity };
+ static const struct rgb_zone off = { 0, 0, 0 };
+
+ drvdata->rgb_frame_count = 2;
+ claw_frame_fill_solid(&drvdata->rgb_frames[0], zone);
+ claw_frame_fill_solid(&drvdata->rgb_frames[1], off);
+
+ return claw_write_rgb_state(drvdata);
+}
+
+/* Apply chroma effect (6 frames: rainbow cycle, all zones sync) */
+static int claw_apply_chroma(struct claw_drvdata *drvdata)
+{
+ static const struct rgb_zone colors[] = {
+ {255, 0, 0}, /* red */
+ {255, 255, 0}, /* yellow */
+ { 0, 255, 0}, /* green */
+ { 0, 255, 255}, /* cyan */
+ { 0, 0, 255}, /* blue */
+ {255, 0, 255}, /* magenta */
+ };
+ u8 frame_count = ARRAY_SIZE(colors);
+ int frame;
+
+ drvdata->rgb_frame_count = frame_count;
+
+ for (frame = 0; frame < frame_count; frame++)
+ claw_frame_fill_solid(&drvdata->rgb_frames[frame], colors[frame]);
+
+ return claw_write_rgb_state(drvdata);
+}
+
+/* Apply rainbow effect (4 frames: rotating colors around joysticks) */
+static int claw_apply_rainbow(struct claw_drvdata *drvdata)
+{
+ static const struct rgb_zone colors[] = {
+ {255, 0, 0}, /* red */
+ { 0, 255, 0}, /* green */
+ { 0, 255, 255}, /* cyan */
+ { 0, 0, 255}, /* blue */
+ };
+ u8 frame_count = ARRAY_SIZE(colors);
+ int frame, zone;
+
+ drvdata->rgb_frame_count = frame_count;
+
+ for (frame = 0; frame < frame_count; frame++) {
+ for (zone = 0; zone < 4; zone++) {
+ drvdata->rgb_frames[frame].zone[zone] = colors[(zone + frame) % 4];
+ drvdata->rgb_frames[frame].zone[zone + 4] = colors[(zone + frame) % 4];
+ }
+ drvdata->rgb_frames[frame].zone[8] = colors[frame];
+ }
+
+ return claw_write_rgb_state(drvdata);
+}
+
+/*
+ * Apply frostfire effect (4 frames: fire vs ice rotating)
+ * Right joystick: fire red -> dark -> ice blue -> dark (clockwise)
+ * Left joystick: ice blue -> dark -> fire red -> dark (counter-clockwise)
+ * ABXY: fire red -> dark -> ice blue -> dark
+ */
+static int claw_apply_frostfire(struct claw_drvdata *drvdata)
+{
+ static const struct rgb_zone colors[] = {
+ {255, 0, 0}, /* fire red */
+ { 0, 0, 0}, /* dark */
+ { 0, 0, 255}, /* ice blue */
+ { 0, 0, 0}, /* dark */
+ };
+ u8 frame_count = ARRAY_SIZE(colors);
+ int frame, zone;
+
+ drvdata->rgb_frame_count = frame_count;
+
+ for (frame = 0; frame < frame_count; frame++) {
+ for (zone = 0; zone < 4; zone++) {
+ drvdata->rgb_frames[frame].zone[zone] = colors[(zone + frame) % 4];
+ drvdata->rgb_frames[frame].zone[zone + 4] = colors[(zone - frame + 6) % 4];
+ }
+ drvdata->rgb_frames[frame].zone[8] = colors[frame];
+ }
+
+ return claw_write_rgb_state(drvdata);
+}
+
+/* Apply current state to device */
+static int claw_apply_rgb_state(struct claw_drvdata *drvdata)
+{
+ static const struct rgb_zone off = { 0, 0, 0 };
+
+ if (!drvdata->rgb_enabled) {
+ drvdata->rgb_frame_count = 1;
+ claw_frame_fill_solid(&drvdata->rgb_frames[0], off);
+ return claw_write_rgb_state(drvdata);
+ }
+
+ switch (drvdata->rgb_effect) {
+ case CLAW_RGB_EFFECT_MONOCOLOR:
+ return claw_apply_monocolor(drvdata);
+ case CLAW_RGB_EFFECT_BREATHE:
+ return claw_apply_breathe(drvdata);
+ case CLAW_RGB_EFFECT_CHROMA:
+ return claw_apply_chroma(drvdata);
+ case CLAW_RGB_EFFECT_RAINBOW:
+ return claw_apply_rainbow(drvdata);
+ case CLAW_RGB_EFFECT_FROSTFIRE:
+ return claw_apply_frostfire(drvdata);
+ default:
+ dev_err(drvdata->led_mc.led_cdev.dev,
+ "No supported rgb_effect selected\n");
+ return -EINVAL;
+ }
+}
+
+static void claw_rgb_queue_fn(struct work_struct *work)
+{
+ struct delayed_work *dwork = container_of(work, struct delayed_work, work);
+ struct claw_drvdata *drvdata = container_of(dwork, struct claw_drvdata, rgb_queue);
+ int ret;
+
+ ret = claw_apply_rgb_state(drvdata);
+ if (ret)
+ dev_err(drvdata->led_mc.led_cdev.dev,
+ "Failed to apply RGB state: %d\n", ret);
+}
+
+static ssize_t effect_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev);
+ struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc);
+ int ret;
+
+ ret = sysfs_match_string(claw_rgb_effect_text, buf);
+ if (ret < 0)
+ return ret;
+
+ drvdata->rgb_effect = ret;
+ mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50));
+
+ return count;
+}
+
+static ssize_t effect_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev);
+ struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc);
+
+ if (drvdata->rgb_effect >= ARRAY_SIZE(claw_rgb_effect_text))
+ return -EINVAL;
+
+ return sysfs_emit(buf, "%s\n", claw_rgb_effect_text[drvdata->rgb_effect]);
+}
+
+static DEVICE_ATTR_RW(effect);
+
+static ssize_t effect_index_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int i, count = 0;
+
+ for (i = 0; i < ARRAY_SIZE(claw_rgb_effect_text); i++)
+ count += sysfs_emit_at(buf, count, "%s ", claw_rgb_effect_text[i]);
+
+ if (count)
+ buf[count - 1] = '\n';
+
+ return count;
+}
+static DEVICE_ATTR_RO(effect_index);
+
+static ssize_t enabled_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev);
+ struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc);
+ bool val;
+ int ret;
+
+ ret = kstrtobool(buf, &val);
+ if (ret)
+ return ret;
+
+ drvdata->rgb_enabled = val;
+ mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50));
+
+ return count;
+}
+
+static ssize_t enabled_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev);
+ struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc);
+
+ return sysfs_emit(buf, "%s\n", drvdata->rgb_enabled ? "true" : "false");
+}
+static DEVICE_ATTR_RW(enabled);
+
+static ssize_t enabled_index_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "true false\n");
+}
+static DEVICE_ATTR_RO(enabled_index);
+
+static ssize_t speed_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev);
+ struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc);
+ unsigned int val, speed;
+ int ret;
+
+ ret = kstrtouint(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ if (val > 20)
+ return -EINVAL;
+
+ /* 0 is fastest, invert value for intuitive userspace speed */
+ speed = 20 - val;
+
+ drvdata->rgb_speed = speed;
+ mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50));
+
+ return count;
+}
+
+static ssize_t speed_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ u8 speed = 20 - drvdata->rgb_speed;
+
+ return sysfs_emit(buf, "%u\n", speed);
+}
+static DEVICE_ATTR_RW(speed);
+
+static ssize_t speed_range_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "0-20\n");
+}
+static DEVICE_ATTR_RO(speed_range);
+
+static void claw_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness _brightness)
+{
+ struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev);
+ struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc);
+
+ mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50));
+}
+
+static struct attribute *claw_rgb_attrs[] = {
+ &dev_attr_effect.attr,
+ &dev_attr_effect_index.attr,
+ &dev_attr_enabled.attr,
+ &dev_attr_enabled_index.attr,
+ &dev_attr_speed.attr,
+ &dev_attr_speed_range.attr,
+ NULL,
+};
+
+static const struct attribute_group rgb_attr_group = {
+ .attrs = claw_rgb_attrs,
+};
+
+static struct mc_subled claw_rgb_subled_info[] = {
+ {
+ .color_index = LED_COLOR_ID_RED,
+ .channel = 0x1,
+ },
+ {
+ .color_index = LED_COLOR_ID_GREEN,
+ .channel = 0x2,
+ },
+ {
+ .color_index = LED_COLOR_ID_BLUE,
+ .channel = 0x3,
+ },
+};
+
static void claw_remove(struct hid_device *hdev);
static void cfg_setup_fn(struct work_struct *work)
@@ -758,6 +1234,13 @@ static void cfg_setup_fn(struct work_struct *work)
claw_remove(drvdata->hdev);
}
+ ret = claw_read_rgb_config(drvdata->hdev);
+ if (ret) {
+ dev_err(drvdata->led_mc.led_cdev.dev,
+ "Failed to setup device, can't read RGB config: %d\n", ret);
+ claw_remove(drvdata->hdev);
+ }
+
/* Add sysfs attributes after we get the device state */
ret = sysfs_create_group(&drvdata->hdev->dev.kobj, &claw_gamepad_attr_group);
if (ret) {
@@ -766,7 +1249,15 @@ static void cfg_setup_fn(struct work_struct *work)
claw_remove(drvdata->hdev);
}
+ ret = device_add_group(drvdata->led_mc.led_cdev.dev, &rgb_attr_group);
+ if (ret) {
+ dev_err(&drvdata->hdev->dev,
+ "Failed to setup device, can't create led attributes: %d\n", ret);
+ claw_remove(drvdata->hdev);
+ }
+
kobject_uevent(&drvdata->hdev->dev.kobj, KOBJ_CHANGE);
+ kobject_uevent(&drvdata->led_mc.led_cdev.dev->kobj, KOBJ_CHANGE);
}
static void cfg_resume_fn(struct work_struct *work)
@@ -776,6 +1267,10 @@ static void cfg_resume_fn(struct work_struct *work)
u8 data[2] = { drvdata->gamepad_mode, drvdata->mkeys_function };
int ret;
+ ret = claw_read_rgb_config(drvdata->hdev);
+ if (ret)
+ dev_err(drvdata->led_mc.led_cdev.dev, "Failed to read RGB config: %d\n", ret);
+
ret = mcu_property_out(drvdata->hdev, CLAW_COMMAND_TYPE_SWITCH_MODE, data,
ARRAY_SIZE(data), 0);
if (ret)
@@ -789,20 +1284,25 @@ static void claw_features_supported(struct claw_drvdata *drvdata)
if (major == 0x01) {
drvdata->bmap_support = true;
- if (minor >= 0x66)
+ if (minor >= 0x66) {
drvdata->bmap_addr = button_mapping_addr_new;
- else
+ drvdata->rgb_addr = rgb_addr_new;
+ } else {
drvdata->bmap_addr = button_mapping_addr_old;
+ drvdata->rgb_addr = rgb_addr_old;
+ }
return;
}
if ((major == 0x02 && minor >= 0x17) || major >= 0x03) {
drvdata->bmap_support = true;
drvdata->bmap_addr = button_mapping_addr_new;
+ drvdata->rgb_addr = rgb_addr_new;
return;
}
drvdata->bmap_support = false;
+ drvdata->rgb_addr = rgb_addr_old;
}
static int claw_probe(struct hid_device *hdev, const struct hid_device_id *id)
@@ -860,12 +1360,36 @@ static int claw_probe(struct hid_device *hdev, const struct hid_device_id *id)
init_completion(&drvdata->send_cmd_complete);
+ /* Initialize RGB LED */
+ INIT_DELAYED_WORK(&drvdata->rgb_queue, &claw_rgb_queue_fn);
+
+ drvdata->led_mc.led_cdev.name = "msi_claw:rgb:joystick_rings";
+ drvdata->led_mc.led_cdev.brightness = 0x50;
+ drvdata->led_mc.led_cdev.max_brightness = 0x64;
+ drvdata->led_mc.led_cdev.color = LED_COLOR_ID_RGB;
+ drvdata->led_mc.led_cdev.brightness_set = claw_led_brightness_set;
+ drvdata->led_mc.num_colors = 3;
+ drvdata->led_mc.subled_info = devm_kmemdup(&hdev->dev, claw_rgb_subled_info,
+ sizeof(claw_rgb_subled_info), GFP_KERNEL);
+ if (!drvdata->led_mc.subled_info) {
+ ret = -ENOMEM;
+ goto err_close;
+ }
+
+ drvdata->rgb_enabled = true;
+
+ ret = devm_led_classdev_multicolor_register(&hdev->dev, &drvdata->led_mc);
+ if (ret)
+ goto err_close;
+
INIT_DELAYED_WORK(&drvdata->cfg_resume, &cfg_resume_fn);
INIT_DELAYED_WORK(&drvdata->cfg_setup, &cfg_setup_fn);
schedule_delayed_work(&drvdata->cfg_setup, msecs_to_jiffies(500));
return 0;
+err_close:
+ hid_hw_close(hdev);
err_stop_hw:
hid_hw_stop(hdev);
err_probe:
@@ -881,6 +1405,9 @@ static void claw_remove(struct hid_device *hdev)
if (drvdata->endpoint == CLAW_XINPUT_CFG_INTF_IN ||
drvdata->endpoint == CLAW_DINPUT_CFG_INTF_IN) {
+ /* Block writes to brightness/multi_intensity during teardown */
+ drvdata->led_mc.led_cdev.brightness_set = NULL;
+ cancel_delayed_work_sync(&drvdata->rgb_queue);
sysfs_remove_group(&hdev->dev.kobj, &claw_gamepad_attr_group);
cancel_delayed_work_sync(&drvdata->cfg_setup);
cancel_delayed_work_sync(&drvdata->cfg_resume);
--
2.53.0
^ permalink raw reply related
* [PATCH 2/4] HID: hid-msi-claw: Add M-key mapping attributes
From: Derek J. Clark @ 2026-05-10 4:35 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Pierre-Loup A . Griffais, Denis Benato, Zhouwang Huang,
Derek J . Clark, linux-input, linux-doc, linux-kernel
In-Reply-To: <20260510043510.442807-1-derekjohn.clark@gmail.com>
Adds attributes that allow for remapping the M-keys with up to 5 values
when in macro mode. There are 2 mappable buttons on the rear of the
device, M1 on the right and M2 on the left. When mapped, the events will
fire from one of three event devices: gamepad buttons will fire from the
device handled by xpad, while keyboard and mouse events will fire from
respectively typed evdevs provided by the input core. Names of each
mapping have been kept as close to the event that will fire from the evdev
as possible, with context added to the ABS_ events on the direction of the
movement.
Initial reverse-engineering and implementation of this feature was done
by Zhouwang Huang. I refactored the overall format to conform to kernel
driver best practices and style guides. Claude was used as an initial
reviewer of this patch.
Assisted-by: Claude:claude-sonnet-4-6
Co-developed-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
drivers/hid/hid-msi-claw.c | 390 ++++++++++++++++++++++++++++++++++++-
1 file changed, 389 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/hid-msi-claw.c b/drivers/hid/hid-msi-claw.c
index 7a3cd940ec49..60694d075d56 100644
--- a/drivers/hid/hid-msi-claw.c
+++ b/drivers/hid/hid-msi-claw.c
@@ -40,6 +40,8 @@
#define CLAW_DINPUT_CFG_INTF_IN 0x82
#define CLAW_XINPUT_CFG_INTF_IN 0x83
+#define CLAW_KEYS_MAX 5
+
enum claw_command_index {
CLAW_COMMAND_TYPE_READ_PROFILE = 0x04,
CLAW_COMMAND_TYPE_READ_PROFILE_ACK = 0x05,
@@ -64,6 +66,17 @@ static const char * const claw_gamepad_mode_text[] = {
[CLAW_GAMEPAD_MODE_DESKTOP] = "desktop",
};
+enum claw_profile_ack_pending {
+ CLAW_NO_PENDING,
+ CLAW_M1_PENDING,
+ CLAW_M2_PENDING,
+};
+
+enum claw_key_index {
+ CLAW_KEY_M1,
+ CLAW_KEY_M2,
+};
+
enum claw_mkeys_function_index {
CLAW_MKEY_FUNCTION_MACRO,
CLAW_MKEY_FUNCTION_COMBO,
@@ -76,6 +89,154 @@ static const char * const claw_mkeys_function_text[] = {
[CLAW_MKEY_FUNCTION_DISABLED] = "disabled",
};
+static const struct {
+ u8 code;
+ const char *name;
+} claw_button_mapping_key_map[] = {
+ /* Gamepad buttons */
+ { 0x01, "ABS_HAT0Y_UP" },
+ { 0x02, "ABS_HAT0Y_DOWN" },
+ { 0x03, "ABS_HAT0X_LEFT" },
+ { 0x04, "ABS_HAT0X_RIGHT" },
+ { 0x05, "BTN_TL" },
+ { 0x06, "BTN_TR" },
+ { 0x07, "BTN_THUMBL" },
+ { 0x08, "BTN_THUMBR" },
+ { 0x09, "BTN_SOUTH" },
+ { 0x0a, "BTN_EAST" },
+ { 0x0b, "BTN_NORTH" },
+ { 0x0c, "BTN_WEST" },
+ { 0x0d, "BTN_MODE" },
+ { 0x0e, "BTN_SELECT" },
+ { 0x0f, "BTN_START" },
+ { 0x13, "BTN_TL2"},
+ { 0x14, "BTN_TR2"},
+ { 0x15, "ABS_Y_UP"},
+ { 0x16, "ABS_Y_DOWN"},
+ { 0x17, "ABS_X_LEFT"},
+ { 0x18, "ABS_X_LEFT_RIGHT"},
+ { 0x19, "ABS_RY_UP"},
+ { 0x1a, "ABS_RY_DOWN"},
+ { 0x1b, "ABS_RX_LEFT"},
+ { 0x1c, "ABS_RX_RIGHT"},
+ /* Keyboard keys */
+ { 0x32, "KEY_ESC" },
+ { 0x33, "KEY_F1" },
+ { 0x34, "KEY_F2" },
+ { 0x35, "KEY_F3" },
+ { 0x36, "KEY_F4" },
+ { 0x37, "KEY_F5" },
+ { 0x38, "KEY_F6" },
+ { 0x39, "KEY_F7" },
+ { 0x3a, "KEY_F8" },
+ { 0x3b, "KEY_F9" },
+ { 0x3c, "KEY_F10" },
+ { 0x3d, "KEY_F11" },
+ { 0x3e, "KEY_F12" },
+ { 0x3f, "KEY_GRAVE" },
+ { 0x40, "KEY_1" },
+ { 0x41, "KEY_2" },
+ { 0x42, "KEY_3" },
+ { 0x43, "KEY_4" },
+ { 0x44, "KEY_5" },
+ { 0x45, "KEY_6" },
+ { 0x46, "KEY_7" },
+ { 0x47, "KEY_8" },
+ { 0x48, "KEY_9" },
+ { 0x49, "KEY_0" },
+ { 0x4a, "KEY_MINUS" },
+ { 0x4b, "KEY_EQUAL" },
+ { 0x4c, "KEY_BACKSPACE" },
+ { 0x4d, "KEY_TAB" },
+ { 0x4e, "KEY_Q" },
+ { 0x4f, "KEY_W" },
+ { 0x50, "KEY_E" },
+ { 0x51, "KEY_R" },
+ { 0x52, "KEY_T" },
+ { 0x53, "KEY_Y" },
+ { 0x54, "KEY_U" },
+ { 0x55, "KEY_I" },
+ { 0x56, "KEY_O" },
+ { 0x57, "KEY_P" },
+ { 0x58, "KEY_LEFTBRACE" },
+ { 0x59, "KEY_RIGHTBRACE" },
+ { 0x5a, "KEY_BACKSLASH" },
+ { 0x5b, "KEY_CAPSLOCK" },
+ { 0x5c, "KEY_A" },
+ { 0x5d, "KEY_S" },
+ { 0x5e, "KEY_D" },
+ { 0x5f, "KEY_F" },
+ { 0x60, "KEY_G" },
+ { 0x61, "KEY_H" },
+ { 0x62, "KEY_J" },
+ { 0x63, "KEY_K" },
+ { 0x64, "KEY_L" },
+ { 0x65, "KEY_SEMICOLON" },
+ { 0x66, "KEY_APOSTROPHE" },
+ { 0x67, "KEY_ENTER" },
+ { 0x68, "KEY_LEFTSHIFT" },
+ { 0x69, "KEY_Z" },
+ { 0x6a, "KEY_X" },
+ { 0x6b, "KEY_C" },
+ { 0x6c, "KEY_V" },
+ { 0x6d, "KEY_B" },
+ { 0x6e, "KEY_N" },
+ { 0x6f, "KEY_M" },
+ { 0x70, "KEY_COMMA" },
+ { 0x71, "KEY_DOT" },
+ { 0x72, "KEY_SLASH" },
+ { 0x73, "KEY_RIGHTSHIFT" },
+ { 0x74, "KEY_LEFTCTRL" },
+ { 0x75, "KEY_LEFTMETA" },
+ { 0x76, "KEY_LEFTALT" },
+ { 0x77, "KEY_SPACE" },
+ { 0x78, "KEY_RIGHTALT" },
+ { 0x79, "KEY_RIGHTCTRL" },
+ { 0x7a, "KEY_INSERT" },
+ { 0x7b, "KEY_HOME" },
+ { 0x7c, "KEY_PAGEUP" },
+ { 0x7d, "KEY_DELETE" },
+ { 0x7e, "KEY_END" },
+ { 0x7f, "KEY_PAGEDOWN" },
+ { 0x8a, "KEY_KPENTER" },
+ { 0x8b, "KEY_KP0" },
+ { 0x8c, "KEY_KP1" },
+ { 0x8d, "KEY_KP2" },
+ { 0x8e, "KEY_KP3" },
+ { 0x8f, "KEY_KP4" },
+ { 0x90, "KEY_KP5" },
+ { 0x91, "KEY_KP6" },
+ { 0x92, "KEY_KP7" },
+ { 0x93, "KEY_KP8" },
+ { 0x94, "KEY_KP9" },
+ { 0x95, "MD_PLAY" },
+ { 0x96, "MD_STOP" },
+ { 0x97, "MD_NEXT" },
+ { 0x98, "MD_PREV" },
+ { 0x99, "MD_VOL_UP" },
+ { 0x9a, "MD_VOL_DOWN" },
+ { 0x9b, "MD_VOL_MUTE" },
+ { 0x9c, "KEY_F23" },
+ /* Mouse events */
+ { 0xc8, "BTN_LEFT" },
+ { 0xc9, "BTN_MIDDLE" },
+ { 0xca, "BTN_RIGHT" },
+ { 0xcb, "BTN_SIDE" },
+ { 0xcc, "BTN_EXTRA" },
+ { 0xcd, "REL_WHEEL_UP" },
+ { 0xce, "REL_WHEEL_DOWN" },
+};
+
+static const u16 button_mapping_addr_old[] = {
+ 0x007a, /* M1 */
+ 0x011f, /* M2 */
+};
+
+static const u16 button_mapping_addr_new[] = {
+ 0x00bb, /* M1 */
+ 0x0164, /* M2 */
+};
+
struct claw_command_report {
u8 report_id;
u8 padding[2];
@@ -86,16 +247,22 @@ struct claw_command_report {
struct claw_drvdata {
/* MCU General Variables */
+ enum claw_profile_ack_pending profile_pending;
struct completion send_cmd_complete;
struct delayed_work cfg_resume;
struct delayed_work cfg_setup;
struct hid_device *hdev;
struct mutex cfg_mutex; /* mutex for synchronous data */
+ u16 bcd_device;
int endpoint;
/* Gamepad Variables */
enum claw_mkeys_function_index mkeys_function;
enum claw_gamepad_mode_index gamepad_mode;
+ u8 m1_codes[CLAW_KEYS_MAX];
+ u8 m2_codes[CLAW_KEYS_MAX];
+ const u16 *bmap_addr;
+ bool bmap_support;
};
static int get_endpoint_address(struct hid_device *hdev)
@@ -123,6 +290,31 @@ static int claw_gamepad_mode_event(struct claw_drvdata *drvdata,
return 0;
}
+static int claw_profile_event(struct claw_drvdata *drvdata, struct claw_command_report *cmd_rep)
+{
+ u8 *codes;
+ int i;
+
+ switch (drvdata->profile_pending) {
+ case CLAW_M1_PENDING:
+ case CLAW_M2_PENDING:
+ codes = (drvdata->profile_pending == CLAW_M1_PENDING) ?
+ drvdata->m1_codes : drvdata->m2_codes;
+ /* Extract key codes; replace disabled (0xff) with 0x00, which is (null) in _show */
+ for (i = 0; i < CLAW_KEYS_MAX; i++)
+ codes[i] = (cmd_rep->data[6 + i] != 0xff) ? cmd_rep->data[6 + i] : 0x00;
+ break;
+ default:
+ dev_warn(&drvdata->hdev->dev,
+ "Got profile event without changes pending from command:%x\n",
+ cmd_rep->cmd);
+ return -EINVAL;
+ }
+ drvdata->profile_pending = CLAW_NO_PENDING;
+
+ return 0;
+}
+
static int claw_raw_event(struct hid_device *hdev, struct hid_report *report,
u8 *data, int size)
{
@@ -149,6 +341,9 @@ static int claw_raw_event(struct hid_device *hdev, struct hid_report *report,
case CLAW_COMMAND_TYPE_GAMEPAD_MODE_ACK:
ret = claw_gamepad_mode_event(drvdata, cmd_rep);
break;
+ case CLAW_COMMAND_TYPE_READ_PROFILE_ACK:
+ ret = claw_profile_event(drvdata, cmd_rep);
+ break;
case CLAW_COMMAND_TYPE_ACK:
break;
default:
@@ -356,6 +551,157 @@ static ssize_t reset_store(struct device *dev, struct device_attribute *attr,
}
static DEVICE_ATTR_WO(reset);
+static int button_mapping_name_to_code(const char *name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(claw_button_mapping_key_map); i++) {
+ if (!strcmp(name, claw_button_mapping_key_map[i].name))
+ return claw_button_mapping_key_map[i].code;
+ }
+
+ return -EINVAL;
+}
+
+static const char *button_mapping_code_to_name(u8 code)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(claw_button_mapping_key_map); i++) {
+ if (claw_button_mapping_key_map[i].code == code)
+ return claw_button_mapping_key_map[i].name;
+ }
+
+ return NULL;
+}
+
+static int claw_buttons_store(struct device *dev, const char *buf, u8 mkey_idx)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ u8 data[] = { 0x01, (drvdata->bmap_addr[mkey_idx] >> 8) & 0xff,
+ drvdata->bmap_addr[mkey_idx] & 0xff, 0x07,
+ 0x04, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff };
+ size_t len = ARRAY_SIZE(data);
+ int ret, key_count, i;
+ char **raw_keys;
+
+ raw_keys = argv_split(GFP_KERNEL, buf, &key_count);
+ if (!raw_keys)
+ return -ENOMEM;
+
+ if (key_count > CLAW_KEYS_MAX) {
+ ret = -EINVAL;
+ goto err_free;
+ }
+
+ if (key_count == 0)
+ goto set_buttons;
+
+ for (i = 0; i < key_count; i++) {
+ ret = button_mapping_name_to_code(raw_keys[i]);
+ if (ret < 0)
+ goto err_free;
+
+ data[6 + i] = ret;
+ }
+
+set_buttons:
+ ret = mcu_property_out(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA, data, len, 8);
+ if (ret < 0)
+ goto err_free;
+
+ ret = mcu_property_out(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 0);
+
+err_free:
+ argv_free(raw_keys);
+ return ret;
+}
+
+static int claw_buttons_show(struct device *dev, char *buf, enum claw_key_index m_key)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ u8 data[] = { 0x01, (drvdata->bmap_addr[m_key] >> 8) & 0xff,
+ drvdata->bmap_addr[m_key] & 0xff, 0x07 };
+ size_t len = ARRAY_SIZE(data);
+ int i, ret, count = 0;
+ const char *name;
+ u8 *codes;
+
+ codes = (m_key == CLAW_KEY_M1) ? drvdata->m1_codes : drvdata->m2_codes;
+ drvdata->profile_pending = (m_key == CLAW_KEY_M1) ? CLAW_M1_PENDING : CLAW_M2_PENDING;
+
+ ret = mcu_property_out(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, data, len, 8);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < CLAW_KEYS_MAX; i++) {
+ name = button_mapping_code_to_name(codes[i]);
+ if (name)
+ count += sysfs_emit_at(buf, count, "%s ", name);
+ }
+
+ if (!count)
+ return sysfs_emit(buf, "(not set)\n");
+
+ buf[count - 1] = '\n';
+
+ return count;
+}
+
+static ssize_t button_m1_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+
+ ret = claw_buttons_store(dev, buf, CLAW_KEY_M1);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t button_m1_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return claw_buttons_show(dev, buf, CLAW_KEY_M1);
+}
+static DEVICE_ATTR_RW(button_m1);
+
+static ssize_t button_m2_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+
+ ret = claw_buttons_store(dev, buf, CLAW_KEY_M2);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t button_m2_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return claw_buttons_show(dev, buf, CLAW_KEY_M2);
+}
+static DEVICE_ATTR_RW(button_m2);
+
+static ssize_t button_mapping_options_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int i, count = 0;
+
+ for (i = 0; i < ARRAY_SIZE(claw_button_mapping_key_map); i++)
+ count += sysfs_emit_at(buf, count, "%s ", claw_button_mapping_key_map[i].name);
+
+ buf[count - 1] = '\n';
+
+ return count;
+}
+static DEVICE_ATTR_RO(button_mapping_options);
+
static umode_t claw_gamepad_attr_is_visible(struct kobject *kobj, struct attribute *attr,
int n)
{
@@ -368,10 +714,22 @@ static umode_t claw_gamepad_attr_is_visible(struct kobject *kobj, struct attribu
return 0;
}
- return attr->mode;
+ /* Always show attrs available on all firmware */
+ if (attr == &dev_attr_gamepad_mode.attr ||
+ attr == &dev_attr_gamepad_mode_index.attr ||
+ attr == &dev_attr_mkeys_function.attr ||
+ attr == &dev_attr_mkeys_function_index.attr ||
+ attr == &dev_attr_reset.attr)
+ return attr->mode;
+
+ /* Hide button mapping attrs if it isn't supported */
+ return drvdata->bmap_support ? attr->mode : 0;
}
static struct attribute *claw_gamepad_attrs[] = {
+ &dev_attr_button_m1.attr,
+ &dev_attr_button_m2.attr,
+ &dev_attr_button_mapping_options.attr,
&dev_attr_gamepad_mode.attr,
&dev_attr_gamepad_mode_index.attr,
&dev_attr_mkeys_function.attr,
@@ -424,6 +782,29 @@ static void cfg_resume_fn(struct work_struct *work)
dev_err(&drvdata->hdev->dev, "Failed to set gamepad mode settings: %d\n", ret);
}
+static void claw_features_supported(struct claw_drvdata *drvdata)
+{
+ u8 major = (drvdata->bcd_device >> 8) & 0xff;
+ u8 minor = drvdata->bcd_device & 0xff;
+
+ if (major == 0x01) {
+ drvdata->bmap_support = true;
+ if (minor >= 0x66)
+ drvdata->bmap_addr = button_mapping_addr_new;
+ else
+ drvdata->bmap_addr = button_mapping_addr_old;
+ return;
+ }
+
+ if ((major == 0x02 && minor >= 0x17) || major >= 0x03) {
+ drvdata->bmap_support = true;
+ drvdata->bmap_addr = button_mapping_addr_new;
+ return;
+ }
+
+ drvdata->bmap_support = false;
+}
+
static int claw_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct claw_drvdata *drvdata;
@@ -470,6 +851,13 @@ static int claw_probe(struct hid_device *hdev, const struct hid_device_id *id)
if (ret)
goto err_stop_hw;
+ /* Determine feature level from firmware version */
+ drvdata->bcd_device = le16_to_cpu(udev->descriptor.bcdDevice);
+ claw_features_supported(drvdata);
+
+ if (!drvdata->bmap_support)
+ dev_warn(&hdev->dev, "M-Key mapping is not supported. Update firmware to enable.\n");
+
init_completion(&drvdata->send_cmd_complete);
INIT_DELAYED_WORK(&drvdata->cfg_resume, &cfg_resume_fn);
--
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