* Re: [PATCH v3 2/2] iio: magnetometer: add support for Melexis MLX90393
From: sashiko-bot @ 2026-06-27 1:10 UTC (permalink / raw)
To: Nikhil Gautam; +Cc: devicetree, robh, conor+dt
In-Reply-To: <20260627005843.7786-3-nikhilgtr@gmail.com>
Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
- [High] TOCTOU data race in `mlx90393_read_avail` due to static array.
- [High] Stack-allocated buffers passed to I2C transport violate DMA rules.
- [High] `mlx90393_check_status` rejects valid responses for 4-axis reads.
- [Medium] Writing to `in_temp_scale` silently corrupts magnetometer gain configuration.
- [Medium] Incorrect physical units exposed for IIO scales (ABI violation).
- [Medium] Temperature oversampling ratio exposes internal register indices (ABI violation).
- [Low] Use of floating-point literal in `fsleep` duration.
- [Low] Misuse of `MLX90393_AXIS_MAX` for scale array length.
- [Low] Data race on configuration variables in `mlx90393_read_raw`.
--
commit 615b644e2712a4949015c117e64d449d197438d8
Author: Nikhil Gautam <nikhilgtr@gmail.com>
iio: magnetometer: add support for Melexis MLX90393
Add Industrial I/O subsystem support for the Melexis MLX90393 3-axis
magnetometer and temperature sensor.
> diff --git a/drivers/iio/magnetometer/mlx90393_core.c b/drivers/iio/magnetometer/mlx90393_core.c
> index 0000000000000..78fcab8a2b566 100644
> --- /dev/null
> +++ b/drivers/iio/magnetometer/mlx90393_core.c
[ ... ]
> +static const int mlx90393_osr2_avail[MLX90393_OSR2_MAX] = {
> + 0, 1, 2, 3,
> +};
[Severity: Medium]
Should this array expose the physical oversampling ratios (e.g., 1, 2, 4, 8)
rather than the internal register values, to comply with IIO ABI standards?
[ ... ]
> +static int mlx90393_check_status(u8 cmd, u8 status)
> +{
> + /* Datasheet: Table 12: Status byte definition */
> +
> + /* Always validate error bit */
> + if (status & MLX90393_STATUS_ERROR)
> + return -EIO;
> +
> + switch (cmd & MLX90393_CMD_MASK) {
> + case MLX90393_CMD_RM:
> + /*
> + * D1:D0 indicates response availability
> + * 00 means invalid/no measurement
> + */
> + if ((status & MLX90393_STATUS_RESP) == 0)
> + return -EIO;
[Severity: High]
Does this validation break 4-axis reads?
If MLX90393_MEASURE_ALL requests 4 words, could the 2-bit counter overflow
to 00, causing the driver to incorrectly reject valid responses?
[ ... ]
> +static int mlx90393_write_reg(struct mlx90393_data *data, u8 reg, u16 val)
> +{
> + u8 tx[4];
> + u8 status;
> + int ret;
> +
> + tx[0] = MLX90393_CMD_WR;
> + put_unaligned_be16(val, &tx[1]);
> + /* Register address is encoded in bits [7:2] */
> + tx[3] = reg << 2;
> +
> + ret = mlx90393_xfer(data, tx, sizeof(tx), &status, 1);
[Severity: High]
Can passing stack-allocated buffers to the I2C transport cause memory
corruption or a kernel panic?
If the underlying I2C controller uses DMA, mapping stack memory (which might
be CONFIG_VMAP_STACK) is generally not allowed. This also applies to the rx
buffer in mlx90393_read_measurement().
[ ... ]
> +static int mlx90393_get_scale(struct mlx90393_data *data,
> + const struct iio_chan_spec *chan,
> + int *val, int *val2)
> +{
> + enum mlx90393_axis_type axis;
> + u8 res;
> +
> + if (chan->channel2 == IIO_MOD_Z) {
> + axis = MLX90393_AXIS_TYPE_Z;
> + res = data->res_z;
> + } else {
> + axis = MLX90393_AXIS_TYPE_XY;
> + res = data->res_xy;
> + }
> +
> + *val = 0;
> + *val2 = mlx90393_scale_table[axis][data->gain_sel][res];
> +
> + return IIO_VAL_INT_PLUS_NANO;
[Severity: Medium]
Do these return values violate the IIO ABI?
Is the magnetometer scale exposing Tesla (nano scale) instead of Gauss?
[ ... ]
> +static int mlx90393_write_raw(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + int val, int val2,
> + long mask)
> +{
> + struct mlx90393_data *data = iio_priv(indio_dev);
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_SCALE: {
> + guard(mutex)(&data->lock);
> + return mlx90393_set_scale(data, chan, val, val2);
> + }
[Severity: Medium]
Does this lack of chan->type checking mean writes to the temperature scale
sysfs attribute will fall through to mlx90393_set_scale()?
Could this unintentionally alter the magnetometer gain configuration?
[ ... ]
> +static int mlx90393_read_raw(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + int *val, int *val2, long mask)
> +{
> + struct mlx90393_data *data = iio_priv(indio_dev);
> + int ret;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW: {
> + guard(mutex)(&data->lock);
> + ret = mlx90393_read_measurement(data, chan->address, val);
> + if (ret)
> + return ret;
> +
> + return IIO_VAL_INT;
> + }
> + case IIO_CHAN_INFO_SCALE:
> + switch (chan->type) {
> + case IIO_MAGN:
> + return mlx90393_get_scale(data, chan, val, val2);
[Severity: Low]
Does reading driver configuration fields like data->gain_sel in
mlx90393_get_scale() without holding data->lock introduce a data race with
concurrent sysfs writes in mlx90393_write_raw()?
> +
> + case IIO_TEMP:
> + /*
> + * Datasheet Table 7: Thermal Specification
> + */
> + *val = 0;
> + *val2 = 22124;
> + return IIO_VAL_INT_PLUS_MICRO;
[Severity: Medium]
Does this violate the IIO ABI by exposing Celsius instead of milli-Celsius?
[ ... ]
> +static int mlx90393_read_avail(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + const int **vals,
> + int *type,
> + int *length,
> + long mask)
> +{
> + struct mlx90393_data *data = iio_priv(indio_dev);
> + static int scale_avail[MLX90393_GAIN_MAX][MLX90393_AXIS_MAX];
[Severity: High]
Is it safe to populate and return a pointer to a static array here?
Since the IIO core reads this array outside the driver's lock, couldn't a
concurrent sysfs read from another thread or device overwrite the array
contents while it's being read?
[ ... ]
> + *vals = &scale_avail[0][0];
> + *type = IIO_VAL_INT_PLUS_NANO;
> + *length = MLX90393_GAIN_MAX * MLX90393_AXIS_MAX;
[Severity: Low]
Is MLX90393_AXIS_MAX being misused here to calculate the length?
While it evaluates to 2, shouldn't length represent the number of values
(entries * 2 elements per IIO value pair) rather than axis count?
[ ... ]
> +static int mlx90393_init(struct mlx90393_data *data)
> +{
> + int ret;
> + u16 reg;
> +
> + /* Exit mode */
> + ret = mlx90393_write_cmd(data, MLX90393_CMD_EX);
> + if (ret)
> + return ret;
> +
> + /*
> + * Datasheet section 15.4.1.2 (RT command), Figure 16:
> + * Wait 1 ms after EX command before issuing RT.
> + */
> + fsleep(1 * USEC_PER_MSEC);
> +
> + /* Reset device */
> + ret = mlx90393_write_cmd(data, MLX90393_CMD_RT);
> + if (ret)
> + return ret;
> +
> + /*
> + * Datasheet section 15.4.1.2 (RT command), Figure 16:
> + * Wait 1.5 ms for the start-up sequence to complete.
> + */
> + fsleep(1.5 * USEC_PER_MSEC);
[Severity: Low]
Could using a floating-point literal (1.5) cause build or linkage errors
on architectures where hardware floating-point arithmetic is not supported
in the kernel?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260627005843.7786-1-nikhilgtr@gmail.com?part=2
^ permalink raw reply
* Re: [PATCH v3 1/2] dt-bindings: iio: magnetometer: add Melexis MLX90393
From: Rob Herring (Arm) @ 2026-06-27 2:26 UTC (permalink / raw)
To: Nikhil Gautam
Cc: jic23, krzk+dt, nuno.sa, linux-iio, devicetree, dlechner,
linux-kernel, u.kleine-koenig, conor+dt, andy
In-Reply-To: <20260627005843.7786-2-nikhilgtr@gmail.com>
On Sat, 27 Jun 2026 06:28:42 +0530, Nikhil Gautam wrote:
> Add devicetree bindings for the Melexis MLX90393
> 3-axis magnetometer and temperature sensor.
>
> The device supports magnetic field and temperature
> measurements over I2C and SPI interfaces.
>
> This initial binding documents the I2C interface.
>
> Signed-off-by: Nikhil Gautam <nikhilgtr@gmail.com>
> ---
> .../iio/magnetometer/melexis,mlx90393.yaml | 54 +++++++++++++++++++
> MAINTAINERS | 6 +++
> 2 files changed, 60 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/iio/magnetometer/melexis,mlx90393.yaml
>
My bot found errors running 'make dt_binding_check' on your patch:
yamllint warnings/errors:
dtschema/dtc warnings/errors:
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/iio/magnetometer/melexis,mlx90393.example.dtb: magnetometer@c (melexis,mlx90393): 'vdd-supply' is a required property
from schema $id: http://devicetree.org/schemas/iio/magnetometer/melexis,mlx90393.yaml
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/iio/magnetometer/melexis,mlx90393.example.dtb: magnetometer@c (melexis,mlx90393): 'vddio-supply' is a required property
from schema $id: http://devicetree.org/schemas/iio/magnetometer/melexis,mlx90393.yaml
doc reference errors (make refcheckdocs):
See https://patchwork.kernel.org/project/devicetree/patch/20260627005843.7786-2-nikhilgtr@gmail.com
The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.
If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:
pip3 install dtschema --upgrade
Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.
^ permalink raw reply
* Re: [PATCH 2/13] dt-bindings: sound: Add Qualcomm QAIF binding
From: Harendra Gautam @ 2026-06-27 5:20 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Srinivas Kandagatla, Mark Brown, Liam Girdwood, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, linux-sound, linux-arm-msm,
devicetree, linux-kernel
In-Reply-To: <0f91b335-35b8-4b71-8601-0d2c4dc53950@oss.qualcomm.com>
On Thu, Jun 25, 2026 at 5:41 PM Konrad Dybcio
<konrad.dybcio@oss.qualcomm.com> wrote:
>
> On 6/24/26 8:59 AM, Harendra Gautam wrote:
> > On Tue, Jun 23, 2026 at 9:18 PM Konrad Dybcio
> > <konrad.dybcio@oss.qualcomm.com> wrote:
> >>
> >> On 6/23/26 2:26 PM, Harendra Gautam wrote:
> >>> On Tue, Jun 9, 2026 at 3:27 PM Konrad Dybcio
> >>> <konrad.dybcio@oss.qualcomm.com> wrote:
>
> [...]
>
> >>>>> + qcom,qaif-aif-sync-mode:
> >>>>> + $ref: /schemas/types.yaml#/definitions/uint32
> >>>>> + description:
> >>>>> + Sync mode. Use QAIF_AIF_SYNC_MODE_SHORT (0) for short (pulse)
> >>>>> + sync or QAIF_AIF_SYNC_MODE_LONG (1) for long (level) sync.
> >>>>> + qcom,qaif-aif-sync-src:
> >>>>> + $ref: /schemas/types.yaml#/definitions/uint32
> >>>>> + description:
> >>>>> + Sync source. Use QAIF_AIF_SYNC_SRC_SLAVE (0) for slave mode
> >>>>> + or QAIF_AIF_SYNC_SRC_MASTER (1) for master mode.
> >>>>
> >>>> Should these be boolean flags then?
> >>> It should not be, the intention is to define explicitly, for better
> >>> readability I can rename these flags as EXTERNAL/INTERNAL, Please
> >>> suggest.
> >>
> >> Are all 4 combinations of them being present/absent valid on shikra?
> >>
> >> Konrad
> > Sorry if I did not put details properly, I will be correcting in the
> > next patch. sync-src can be external clock or internal clock, where 0
> > means external and 1 means internal.
>
> What I meant to ask is whether all combinations of qcom,qaif-aif-sync-mode
> and qcom,qaif-aif-sync-src being present/absent (set to 0/1) are actually
> valid and should be supported
>
> Konrad
Apologies, the correct sync-mode mapping is SHORT (0), ONE_SLOT (1),
and LONG (2) — ONE_SLOT was missing and LONG was incorrectly assigned
(1). This will be fixed in the next patch. Since sync-mode and
sync-src are independent controls, all combinations are valid and both
properties are retained as explicit uint32 fields for flexibility. On
Shikra, the validated combination is ONE_SLOT with INTERNAL sync
source.
-Harendra
^ permalink raw reply
* Re: [PATCH 2/2] spi: qcom-geni: Add property to force GSI mode
From: Pengyu Luo @ 2026-06-27 5:22 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Mukesh Savaliya, Dmitry Baryshkov, Mark Brown, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Andy Gross, Bjorn Andersson,
linux-arm-msm, linux-spi, devicetree, linux-kernel
In-Reply-To: <c22f82e1-65a8-4472-924d-91908f4d6104@oss.qualcomm.com>
On Fri, Jun 26, 2026 at 11:34 PM Konrad Dybcio
<konrad.dybcio@oss.qualcomm.com> wrote:
>
> On 6/25/26 11:06 AM, Mukesh Savaliya wrote:
> > Hi Konrad, sorry for late response.
> >
> > On 6/15/2026 5:12 PM, Konrad Dybcio wrote:
> >> On 6/15/26 6:25 AM, Pengyu Luo wrote:
> >>> On Mon, Jun 15, 2026 at 5:18 AM Dmitry Baryshkov
> >>> <dmitry.baryshkov@oss.qualcomm.com> wrote:
> >>>>
> >>>> On Sun, Jun 14, 2026 at 04:34:24PM +0800, Pengyu Luo wrote:
> >>>>> Some devices (such as gaokun3) do not disable FIFO mode, causing the
> >>>>> driver to fallback to FIFO mode by default. However, these platforms
> >>>>> also support GSI mode, which is highly preferred for certain
> >>>>> peripherals like SPI touchscreens to improve performance.
> >>>>>
> >>>>> Introduce the "qcom,force-gsi-mode" device property to hint and force
> >>>>> the controller into GSI mode during initialization.
> >>>>
> >>>> Ideally, this should be decided by the SPI controller based on the
> >>>> requirements. Another option would be to prefer GSI for all transfers if
> >>>> it is available, ignoring the FIFO even if it is not disabled.
> >>>>
> >>>
> >>> I have read reviews. Can we check if the compatible
> >>> "qcom,sm6350-gpi-dma" is in DT? If the gpi dma controller is
> >>> presented, then we enable GSI mode.
> >>
> >>
> >> This hunk is very odd:
> >>
> >> /*
> >> * in case of failure to get gpi dma channel, we can still do the
> >> * FIFO mode, so fallthrough
> >> */
> >> dev_warn(mas->dev, "FIFO mode disabled, but couldn't get DMA, fall back to FIFO mode\n");
> >>
> >> In my understanding, GSI DMA mode is always preferable. +Mukesh, do
> >> you have any insights?
> >>
> > GSI mode is preferable but if for some reason it fails, we try to continue with the FIFO mode. Just fallback mechanism.
>
> So, would making GSI the default and FIFO the fallback option
> sound good?
>
Yes, I have sent v2, please check here
https://lore.kernel.org/linux-arm-msm/20260616122605.668908-1-mitltlatltl@gmail.com
Best wishes,
Pengyu
> Konrad
>
^ permalink raw reply
* [PATCH v1] ARM: dts: aspeed: g6: Use KCS AST2600 compatible string
From: Haiyue Wang @ 2026-06-27 5:27 UTC (permalink / raw)
To: devicetree, Andrew Jeffery
Cc: Haiyue Wang, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Joel Stanley, moderated list:ARM/ASPEED MACHINE SUPPORT,
moderated list:ARM/ASPEED MACHINE SUPPORT, open list
As the commit 2596f6b93a60 ("ipmi: kcs: aspeed: Add AST2600 compatible
string") does, change the AST2600 DTS file to use kcs ast2600 compatible
string.
Signed-off-by: Haiyue Wang <haiyuewa@163.com>
---
arch/arm/boot/dts/aspeed/aspeed-g6.dtsi | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi b/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
index 56bb3b0444f7..774d5af495fb 100644
--- a/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
+++ b/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
@@ -585,7 +585,7 @@ lpc: lpc@1e789000 {
ranges = <0x0 0x1e789000 0x1000>;
kcs1: kcs@24 {
- compatible = "aspeed,ast2500-kcs-bmc-v2";
+ compatible = "aspeed,ast2600-kcs-bmc";
reg = <0x24 0x1>, <0x30 0x1>, <0x3c 0x1>;
interrupts = <GIC_SPI 138 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&syscon ASPEED_CLK_GATE_LCLK>;
@@ -594,7 +594,7 @@ kcs1: kcs@24 {
};
kcs2: kcs@28 {
- compatible = "aspeed,ast2500-kcs-bmc-v2";
+ compatible = "aspeed,ast2600-kcs-bmc";
reg = <0x28 0x1>, <0x34 0x1>, <0x40 0x1>;
interrupts = <GIC_SPI 139 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&syscon ASPEED_CLK_GATE_LCLK>;
@@ -602,7 +602,7 @@ kcs2: kcs@28 {
};
kcs3: kcs@2c {
- compatible = "aspeed,ast2500-kcs-bmc-v2";
+ compatible = "aspeed,ast2600-kcs-bmc";
reg = <0x2c 0x1>, <0x38 0x1>, <0x44 0x1>;
interrupts = <GIC_SPI 140 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&syscon ASPEED_CLK_GATE_LCLK>;
@@ -610,7 +610,7 @@ kcs3: kcs@2c {
};
kcs4: kcs@114 {
- compatible = "aspeed,ast2500-kcs-bmc-v2";
+ compatible = "aspeed,ast2600-kcs-bmc";
reg = <0x114 0x1>, <0x118 0x1>, <0x11c 0x1>;
interrupts = <GIC_SPI 141 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&syscon ASPEED_CLK_GATE_LCLK>;
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v1] ARM: dts: aspeed: g6: Use KCS AST2600 compatible string
From: sashiko-bot @ 2026-06-27 5:35 UTC (permalink / raw)
To: Haiyue Wang; +Cc: conor+dt, devicetree, robh
In-Reply-To: <20260627052708.172918-1-haiyuewa@163.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] The KCS BMC drivers register and enable their IRQ handler before initializing the device spinlock, leading to a potential crash or lockdep splat on an uninitialized spinlock.
--
commit a51ce0f5a1fd9344e679346b6911e87b35021057
Author: Haiyue Wang <haiyuewa@163.com>
ARM: dts: aspeed: g6: Use KCS AST2600 compatible string
This commit updates the device tree source for the AST2600 to use the
specific aspeed,ast2600-kcs-bmc compatible string instead of the older
ast2500 compatible string.
> diff --git a/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi b/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
> index 189bc3bbb47c9..9d09d6e0092a8 100644
> --- a/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
> +++ b/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
> @@ -576,7 +576,7 @@ lpc: lpc@1e789000 {
> ranges = <0x0 0x1e789000 0x1000>;
>
> kcs1: kcs@24 {
> - compatible = "aspeed,ast2500-kcs-bmc-v2";
> + compatible = "aspeed,ast2600-kcs-bmc";
[Severity: High]
This is a pre-existing issue, but modifying the compatible string draws
attention to an issue in the underlying driver's probe routine.
Does the driver initialization sequence have a race condition that could
lead to a crash on an uninitialized spinlock?
Looking at the driver for this compatible string:
drivers/char/ipmi/kcs_bmc_aspeed.c:aspeed_kcs_probe() {
...
rc = aspeed_kcs_config_downstream_irq(kcs_bmc, pdev);
if (rc)
return rc;
...
aspeed_kcs_enable_channel(kcs_bmc, true);
rc = kcs_bmc_add_device(&priv->kcs_bmc);
...
}
The IRQ handler is registered in aspeed_kcs_config_downstream_irq() and
hardware interrupts are explicitly enabled via aspeed_kcs_enable_channel()
before kcs_bmc_add_device() is called. However, kcs_bmc_add_device() is
where spin_lock_init() initializes the device lock.
If an interrupt fires (either from the hardware or on a shared IRQ line)
in the small window between enabling the channel and initializing the
spinlock, will kcs_bmc_handle_event() unconditionally attempt to acquire
the uninitialized spinlock?
As an aside, the same initialization order issue appears to be present in
the npcm7xx_kcs_probe() routine as well:
drivers/char/ipmi/kcs_bmc_npcm7xx.c:npcm7xx_kcs_probe() {
...
rc = npcm7xx_kcs_config_irq(kcs_bmc, pdev);
if (rc)
return rc;
npcm7xx_kcs_irq_mask_update(kcs_bmc, ...);
npcm7xx_kcs_enable_channel(kcs_bmc, true);
rc = kcs_bmc_add_device(kcs_bmc);
...
}
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260627052708.172918-1-haiyuewa@163.com?part=1
^ permalink raw reply
* Re: [PATCH v4 0/2] cpufreq: spacemit: Add cpufreq support for K1 SoC
From: Gong Shuai @ 2026-06-27 8:40 UTC (permalink / raw)
To: Shuwei Wu, Rafael J. Wysocki, Viresh Kumar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Yixun Lan
Cc: linux-pm, linux-kernel, linux-riscv, spacemit, devicetree
In-Reply-To: <20260626-shadow-deps-v4-0-bba9831f2f1d@mailbox.org>
On 6/26/2026 4:10 PM, Shuwei Wu wrote:
> This series enables CPU DVFS for the SpacemiT K1 SoC using the generic
> cpufreq-dt driver.
>
> K1 has two CPU clock clusters. The two clusters have separate CPU clocks,
> so they are represented as two cpufreq policies: policy0 for CPUs 0-3 and
> policy4 for CPUs 4-7.
>
> The CPU voltage rail is shared between the clusters. To model this with two
> policies, the OPP entries describe voltage ranges instead of a single fixed
> voltage, so the shared regulator can keep the rail within a range acceptable
> for the active OPP constraints.
>
> Tested on Banana Pi BPI-F3:
>
> ~ # cat /sys/devices/system/cpu/online
> 0-7
>
> ~ # ls /sys/devices/system/cpu/cpufreq/
> policy0 policy4
>
> ~ # cat /sys/devices/system/cpu/cpufreq/policy0/scaling_driver
> cpufreq-dt
> ~ # cat /sys/devices/system/cpu/cpufreq/policy0/affected_cpus
> 0 1 2 3
>
> ~ # cat /sys/devices/system/cpu/cpufreq/policy4/scaling_driver
> cpufreq-dt
> ~ # cat /sys/devices/system/cpu/cpufreq/policy4/affected_cpus
> 4 5 6 7
>
> Both policies expose the same OPP frequencies:
>
> ~ # cat /sys/devices/system/cpu/cpufreq/policy0/scaling_available_frequencies
> 614400 819000 1000000 1228800 1600000
>
> ~ # cat /sys/devices/system/cpu/cpufreq/policy4/scaling_available_frequencies
> 614400 819000 1000000 1228800 1600000
>
> For each policy, scaling_setspeed was set to each supported OPP and the
> workload was pinned to one CPU covered by that policy with taskset.
> CPU0 was used for policy0, and CPU4 was used for policy4. The clock rates below
> are from /sys/kernel/debug/clk/clk_summary.
>
> policy0 / CPU0:
> ----------------------------------------------------------
> Frequency | cpu_c0_core_clk | Real (s) | User (s)
> (kHz) | (Hz) | |
> -------------+-------------------+------------+-----------
> 1,600,000 | 1,600,000,000 | 1.81 | 1.80
> 1,228,800 | 1,228,800,000 | 2.37 | 2.37
> 1,000,000 | 1,000,000,000 | 2.89 | 2.89
> 819,000 | 819,200,000 | 3.56 | 3.55
> 614,400 | 614,400,000 | 4.71 | 4.71
> ----------------------------------------------------------
>
> policy4 / CPU4:
> ----------------------------------------------------------
> Frequency | cpu_c1_core_clk | Real (s) | User (s)
> (kHz) | (Hz) | |
> -------------+-------------------+------------+-----------
> 1,600,000 | 1,600,000,000 | 1.81 | 1.80
> 1,228,800 | 1,228,800,000 | 2.36 | 2.36
> 1,000,000 | 1,000,000,000 | 2.89 | 2.89
> 819,000 | 819,200,000 | 3.55 | 3.55
> 614,400 | 614,400,000 | 4.71 | 4.70
> ----------------------------------------------------------
>
> Signed-off-by: Shuwei Wu <shuwei.wu@mailbox.org>
Hi Shuwei,
Thanks for your work.
I have tested this series on the OrangePi RV2 4GB board on top of
next-20260626, with Vincent's patch for OrangePi RV2 applied.
# uname -a
Linux orangepi-rv2 7.1.0-next-20260626-00005-g66ef697be46d #1 SMP PREEMPT Sat Jun 27 14:53:02 CST 2026 riscv64 GNU/Linux
# ls /sys/devices/system/cpu/cpufreq/
boost policy0 policy4
# cat /sys/devices/system/cpu/cpufreq/policy*/scaling_driver
cpufreq-dt
cpufreq-dt
# cat /sys/devices/system/cpu/cpufreq/policy0/affected_cpus
0 1 2 3
# cat /sys/devices/system/cpu/cpufreq/policy4/affected_cpus
4 5 6 7
# cat /sys/devices/system/cpu/cpufreq/policy*/scaling_available_frequencies
614400 819000 1000000 1228800 1600000
614400 819000 1000000 1228800 1600000
I used the following test script to verify cpufreq functionality:
```bash
#!/bin/sh
echo userspace > /sys/devices/system/cpu/cpufreq/policy0/scaling_governor
echo userspace > /sys/devices/system/cpu/cpufreq/policy4/scaling_governor
echo "--- same frequency test ---"
for freq in 614400 819000 1000000 1228800 1600000; do
echo $freq > /sys/devices/system/cpu/cpufreq/policy0/scaling_setspeed
echo $freq > /sys/devices/system/cpu/cpufreq/policy4/scaling_setspeed
sleep 1
t=$(taskset -c 0 time -f "%e" dd if=/dev/zero of=/dev/null bs=1M count=1000 2>&1 | tail -1)
echo "$freq kHz: ${t}s"
done
echo "--- mixed frequency test ---"
echo 614400 > /sys/devices/system/cpu/cpufreq/policy0/scaling_setspeed
echo 1600000 > /sys/devices/system/cpu/cpufreq/policy4/scaling_setspeed
sleep 1
t0=$(taskset -c 0 time -f "%e" dd if=/dev/zero of=/dev/null bs=1M count=1000 2>&1 | tail -1)
t4=$(taskset -c 4 time -f "%e" dd if=/dev/zero of=/dev/null bs=1M count=1000 2>&1 | tail -1)
echo "p0=614400 p4=1600000 -> cpu0=${t0}s cpu4=${t4}s"
echo 1600000 > /sys/devices/system/cpu/cpufreq/policy0/scaling_setspeed
echo 614400 > /sys/devices/system/cpu/cpufreq/policy4/scaling_setspeed
sleep 1
t0=$(taskset -c 0 time -f "%e" dd if=/dev/zero of=/dev/null bs=1M count=1000 2>&1 | tail -1)
t4=$(taskset -c 4 time -f "%e" dd if=/dev/zero of=/dev/null bs=1M count=1000 2>&1 | tail -1)
echo "p0=1600000 p4=614400 -> cpu0=${t0}s cpu4=${t4}s"
echo 1000000 > /sys/devices/system/cpu/cpufreq/policy0/scaling_setspeed
echo 1228800 > /sys/devices/system/cpu/cpufreq/policy4/scaling_setspeed
sleep 1
t0=$(taskset -c 0 time -f "%e" dd if=/dev/zero of=/dev/null bs=1M count=1000 2>&1 | tail -1)
t4=$(taskset -c 4 time -f "%e" dd if=/dev/zero of=/dev/null bs=1M count=1000 2>&1 | tail -1)
echo "p0=1000000 p4=1228800 -> cpu0=${t0}s cpu4=${t4}s"
```
Results:
# ./test_cpufreq_userspace.sh
--- same frequency test ---
614400 kHz: 0.90s
819000 kHz: 0.67s
1000000 kHz: 0.55s
1228800 kHz: 0.45s
1600000 kHz: 0.34s
--- mixed frequency test ---
p0=614400 p4=1600000 -> cpu0=0.90s cpu4=0.34s
p0=1600000 p4=614400 -> cpu0=0.34s cpu4=0.90s
p0=1000000 p4=1228800 -> cpu0=0.55s cpu4=0.45s
Tested-by: Gong Shuai <gsh517025@gmail.com> # OrangePi-RV2
Regards,
Shuai
> ---
> Changes in v4:
> - Represent K1 as two cpufreq-dt policies, one per CPU clock cluster
> - Use OPP voltage ranges for the shared CPU supply
> - Link to v3: https://lore.kernel.org/r/20260612-shadow-deps-v3-0-2f3ba88611ff@mailbox.org
>
> Changes in v3:
> - Add a K1-specific cpufreq driver for the shared-rail, dual-clock topology
> - Use one shared CPU OPP table and one cpufreq policy for all CPUs
> - Link to v2: https://lore.kernel.org/r/20260410-shadow-deps-v2-0-4e16b8c0f60e@mailbox.org
>
> Changes in v2:
> - Move OPP tables to dedicated k1-opp.dtsi
> - Enable OPP only on BPI-F3 with cpu-supply present
> - Link to v1: https://lore.kernel.org/r/20260308-shadow-deps-v1-0-0ceb5c7c07eb@mailbox.org
>
> ---
> Shuwei Wu (2):
> cpufreq: dt-platdev: Add SpacemiT K1 SoC to the allowlist
> riscv: dts: spacemit: Add cpu scaling for K1 SoC
>
> arch/riscv/boot/dts/spacemit/k1-bananapi-f3.dts | 35 +++++++-
> arch/riscv/boot/dts/spacemit/k1-opp.dtsi | 105 ++++++++++++++++++++++++
> arch/riscv/boot/dts/spacemit/k1.dtsi | 8 ++
> drivers/cpufreq/cpufreq-dt-platdev.c | 2 +
> 4 files changed, 149 insertions(+), 1 deletion(-)
> ---
> base-commit: 5164e95565d3fd508ca8a95351323f5716dfb695
> change-id: 20260307-shadow-deps-3582a78aa756
> prerequisite-patch-id: 154bd4f720ce5065d58b988de8f273207b44572e
> prerequisite-message-id: <20260206-spacemit-p1-v4-0-8f695d93811e@riscstar.com>
> prerequisite-patch-id: 5da3e75b18291a5540d4f66d7a0600fb8975ef62
> prerequisite-patch-id: bcf41917414ecef8cf743095d130f6004c32f6a5
> prerequisite-patch-id: cfe3800f8c791ec4c63e070af9628e88e0fc31b9
> prerequisite-message-id: <20260305-k1-clk-fix-v1-1-abca85d6e266@mailbox.org>
> prerequisite-patch-id: 7c7fb9f87dba019ece4c97c45750349a7cd28f3a
>
> Best regards,
^ permalink raw reply
* Re: [External] [PATCH v2 3/8] riscv: Add support for srmcfg CSR from Ssqosid extension
From: yunhui cui @ 2026-06-27 9:11 UTC (permalink / raw)
To: Drew Fustini
Cc: Adrien Ricciardi, Alexandre Ghiti, Atish Kumar Patra, Atish Patra,
Babu Moger, Ben Horgan, Borislav Petkov, Chen Pei, Conor Dooley,
Conor Dooley, Dave Hansen, Dave Martin, Fenghua Yu, Gong Shuai,
Gong Shuai, guo.wenjia23, James Morse, Kornel Dulęba,
Krzysztof Kozlowski, liu.qingtao2, Liu Zhiwei, Palmer Dabbelt,
Paul Walmsley, Peter Newman, Radim Krčmář,
Reinette Chatre, Rob Herring, Samuel Holland,
Sebastian Andrzej Siewior, Tony Luck, Vasudevan Srinivasan,
Ved Shanbhogue, Weiwei Li, linux-kernel, linux-riscv, x86,
devicetree, linux-rt-devel, linux-doc
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-3-2f8049fd902b@kernel.org>
Hi Drew,
On Thu, Jun 25, 2026 at 9:40 AM Drew Fustini <fustini@kernel.org> wrote:
>
> Add support for the srmcfg CSR defined in the Ssqosid ISA extension.
> The CSR contains two fields:
>
> - Resource Control ID (RCID) for resource allocation
> - Monitoring Counter ID (MCID) for tracking resource usage
>
> Requests from a hart to shared resources are tagged with these IDs,
> allowing resource usage to be associated with the running task.
>
> Add a srmcfg field to thread_struct with the same format as the CSR so
> the scheduler can set the RCID and MCID for each task on context
> switch. A per-cpu cpu_srmcfg variable mirrors the CSR state to avoid
> redundant writes. L1D-hot memory access is faster than a CSR read and
> avoids traps under virtualization.
>
> A per-cpu cpu_srmcfg_default holds the default srmcfg for each CPU as
> set by resctrl CPU group assignment. On context switch, RCID and MCID
> inherit from the CPU default independently: a task whose thread RCID
> field is zero takes the CPU default's RCID, and likewise for MCID.
>
> Link: https://github.com/riscv/riscv-ssqosid/releases/tag/v1.0
> Assisted-by: Claude:claude-opus-4-7
> Co-developed-by: Kornel Dulęba <mindal@semihalf.com>
> Signed-off-by: Kornel Dulęba <mindal@semihalf.com>
> Signed-off-by: Drew Fustini <fustini@kernel.org>
> ---
> MAINTAINERS | 8 ++++
> arch/riscv/Kconfig | 18 +++++++
> arch/riscv/include/asm/csr.h | 5 ++
> arch/riscv/include/asm/processor.h | 3 ++
> arch/riscv/include/asm/qos.h | 83 ++++++++++++++++++++++++++++++++
> arch/riscv/include/asm/switch_to.h | 3 ++
> arch/riscv/kernel/Makefile | 2 +
> arch/riscv/kernel/qos.c | 98 ++++++++++++++++++++++++++++++++++++++
> 8 files changed, 220 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 0b9d7c8276acbafdb28a0ea5e81aa853ebee50b9..07109e1a8f8470377916c98074ab68fec51dfdc6 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -23293,6 +23293,14 @@ F: drivers/perf/riscv_pmu.c
> F: drivers/perf/riscv_pmu_legacy.c
> F: drivers/perf/riscv_pmu_sbi.c
>
> +RISC-V QOS RESCTRL SUPPORT
> +M: Drew Fustini <fustini@kernel.org>
> +R: yunhui cui <cuiyunhui@bytedance.com>
> +L: linux-riscv@lists.infradead.org
> +S: Supported
> +F: arch/riscv/include/asm/qos.h
> +F: arch/riscv/kernel/qos.c
> +
> RISC-V RPMI AND MPXY DRIVERS
> M: Rahul Pathak <rahul@summations.net>
> M: Anup Patel <anup@brainfault.org>
> diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
> index 3f0a647218e407f72890e83722ba8472858c1a59..ee586925f97227668c228b5481c05a2f914d928c 100644
> --- a/arch/riscv/Kconfig
> +++ b/arch/riscv/Kconfig
> @@ -590,6 +590,24 @@ config RISCV_ISA_SVNAPOT
>
> If you don't know what to do here, say Y.
>
> +config RISCV_ISA_SSQOSID
> + bool "Ssqosid extension support for supervisor mode Quality of Service ID"
> + depends on 64BIT
> + default n
> + help
> + Adds support for the Ssqosid ISA extension (Supervisor-mode
> + Quality of Service ID).
> +
> + Ssqosid defines the srmcfg CSR which allows the system to tag the
> + running process with an RCID (Resource Control ID) and MCID
> + (Monitoring Counter ID). The RCID is used to determine resource
> + allocation. The MCID is used to track resource usage in event
> + counters.
> +
> + For example, a cache controller may use the RCID to apply a
> + cache partitioning scheme and use the MCID to track how much
> + cache a process, or a group of processes, is using.
> +
> config RISCV_ISA_SVPBMT
> bool "Svpbmt extension support for supervisor mode page-based memory types"
> depends on 64BIT && MMU
> diff --git a/arch/riscv/include/asm/csr.h b/arch/riscv/include/asm/csr.h
> index 31b8988f4488daa89b854ccc97c4efe1c82bcc3e..7bce928e5daa09bd62f0917279b04cfad30f46f5 100644
> --- a/arch/riscv/include/asm/csr.h
> +++ b/arch/riscv/include/asm/csr.h
> @@ -84,6 +84,10 @@
> #define SATP_ASID_MASK _AC(0xFFFF, UL)
> #endif
>
> +/* SRMCFG fields */
> +#define SRMCFG_RCID_MASK GENMASK(11, 0)
> +#define SRMCFG_MCID_MASK GENMASK(27, 16)
> +
> /* Exception cause high bit - is an interrupt if set */
> #define CAUSE_IRQ_FLAG (_AC(1, UL) << (__riscv_xlen - 1))
>
> @@ -328,6 +332,7 @@
> #define CSR_STVAL 0x143
> #define CSR_SIP 0x144
> #define CSR_SATP 0x180
> +#define CSR_SRMCFG 0x181
>
> #define CSR_STIMECMP 0x14D
> #define CSR_STIMECMPH 0x15D
> diff --git a/arch/riscv/include/asm/processor.h b/arch/riscv/include/asm/processor.h
> index 812517b2cec1350f741849c1c56a35027321ef50..49a386d74cd3f0603a3ff919059d077a7e4d513c 100644
> --- a/arch/riscv/include/asm/processor.h
> +++ b/arch/riscv/include/asm/processor.h
> @@ -123,6 +123,9 @@ struct thread_struct {
> /* A forced icache flush is not needed if migrating to the previous cpu. */
> unsigned int prev_cpu;
> #endif
> +#ifdef CONFIG_RISCV_ISA_SSQOSID
> + u32 srmcfg;
> +#endif
> };
>
> /* Whitelist the fstate from the task_struct for hardened usercopy */
> diff --git a/arch/riscv/include/asm/qos.h b/arch/riscv/include/asm/qos.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..e9e1d69f3797be5f89785a9b3aa7d9d51c476a8a
> --- /dev/null
> +++ b/arch/riscv/include/asm/qos.h
> @@ -0,0 +1,83 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef _ASM_RISCV_QOS_H
> +#define _ASM_RISCV_QOS_H
> +
> +#include <linux/percpu-defs.h>
> +
> +#ifdef CONFIG_RISCV_ISA_SSQOSID
> +
> +#include <linux/bitfield.h>
> +#include <linux/cpufeature.h>
> +#include <linux/sched.h>
> +
> +#include <asm/csr.h>
> +#include <asm/fence.h>
> +#include <asm/hwcap.h>
> +
> +/* cached value of srmcfg csr for each cpu */
> +DECLARE_PER_CPU(u32, cpu_srmcfg);
> +
> +/* default srmcfg value for each cpu, set via resctrl cpu assignment */
> +DECLARE_PER_CPU(u32, cpu_srmcfg_default);
> +
> +static inline void __switch_to_srmcfg(struct task_struct *next)
> +{
> + u32 thread_srmcfg, default_srmcfg;
> +
> + thread_srmcfg = READ_ONCE(next->thread.srmcfg);
> + default_srmcfg = __this_cpu_read(cpu_srmcfg_default);
> +
> + /*
> + * RCID and MCID inherit from cpu_srmcfg_default independently.
> + * RESCTRL_RESERVED_CLOSID and RESCTRL_RESERVED_RMID are both 0, so a
> + * zero field means "unassigned" and takes the CPU default.
> + */
> + if (thread_srmcfg == 0) {
> + thread_srmcfg = default_srmcfg;
> + } else {
> + u32 rcid = FIELD_GET(SRMCFG_RCID_MASK, thread_srmcfg);
> + u32 mcid = FIELD_GET(SRMCFG_MCID_MASK, thread_srmcfg);
> +
> + if (rcid == 0 || mcid == 0) {
> + if (rcid == 0)
> + rcid = FIELD_GET(SRMCFG_RCID_MASK, default_srmcfg);
> + if (mcid == 0)
> + mcid = FIELD_GET(SRMCFG_MCID_MASK, default_srmcfg);
> + thread_srmcfg = FIELD_PREP(SRMCFG_RCID_MASK, rcid) |
> + FIELD_PREP(SRMCFG_MCID_MASK, mcid);
> + }
> + }
> +
> + if (thread_srmcfg != __this_cpu_read(cpu_srmcfg)) {
> + /*
> + * Drain stores from the outgoing task before the CSR write
> + * so they retain the previous RCID/MCID tag at the cache
> + * interconnect.
> + */
> + RISCV_FENCE(rw, o);
> +
> + __this_cpu_write(cpu_srmcfg, thread_srmcfg);
> + csr_write(CSR_SRMCFG, thread_srmcfg);
> + /*
> + * Order the csrw before the new task's loads/stores so they
> + * pick up the new tag. Zicsr 6.1.1 makes CSR writes weakly
> + * ordered (device-output) vs memory ops. Ssqosid v1.0 is
> + * silent so honor the general CSR rule.
> + */
> + RISCV_FENCE(o, rw);
This is in the context-switch path and may be expensive in practice. Even if
the target workload is pinned and grouped, unpinned/default-group tasks or
kworkers may still run on those CPUs, causing frequent SRMCFG transitions and
paying two fences each time.
Is this strict ordering required by the Ssqosid spec or known hardware? If
not, can we make this a trade-off and avoid the fences by default, accepting a
small QoS-tagging inaccuracy around the context-switch boundary?
> + }
> +}
> +
> +static __always_inline bool has_srmcfg(void)
> +{
> + return riscv_has_extension_unlikely(RISCV_ISA_EXT_SSQOSID);
> +}
> +
> +#else /* ! CONFIG_RISCV_ISA_SSQOSID */
> +
> +struct task_struct;
> +static __always_inline bool has_srmcfg(void) { return false; }
> +static inline void __switch_to_srmcfg(struct task_struct *next) { }
> +
> +#endif /* CONFIG_RISCV_ISA_SSQOSID */
> +#endif /* _ASM_RISCV_QOS_H */
> diff --git a/arch/riscv/include/asm/switch_to.h b/arch/riscv/include/asm/switch_to.h
> index 0e71eb82f920cac2f14bb626879bb219a2f247cc..1c7ea53ec012adeaf03bf7c5d549ab21849768b5 100644
> --- a/arch/riscv/include/asm/switch_to.h
> +++ b/arch/riscv/include/asm/switch_to.h
> @@ -14,6 +14,7 @@
> #include <asm/processor.h>
> #include <asm/ptrace.h>
> #include <asm/csr.h>
> +#include <asm/qos.h>
>
> #ifdef CONFIG_FPU
> extern void __fstate_save(struct task_struct *save_to);
> @@ -119,6 +120,8 @@ do { \
> __switch_to_fpu(__prev, __next); \
> if (has_vector() || has_xtheadvector()) \
> __switch_to_vector(__prev, __next); \
> + if (has_srmcfg()) \
> + __switch_to_srmcfg(__next); \
> if (switch_to_should_flush_icache(__next)) \
> local_flush_icache_all(); \
> __switch_to_envcfg(__next); \
> diff --git a/arch/riscv/kernel/Makefile b/arch/riscv/kernel/Makefile
> index cabb99cadfb6d1e1284d6b4e9ae76044d36949f5..ebe1c3588177b4b825a52af9ca17e17b5561427c 100644
> --- a/arch/riscv/kernel/Makefile
> +++ b/arch/riscv/kernel/Makefile
> @@ -128,3 +128,5 @@ obj-$(CONFIG_ACPI_NUMA) += acpi_numa.o
>
> obj-$(CONFIG_GENERIC_CPU_VULNERABILITIES) += bugs.o
> obj-$(CONFIG_RISCV_USER_CFI) += usercfi.o
> +
> +obj-$(CONFIG_RISCV_ISA_SSQOSID) += qos.o
> diff --git a/arch/riscv/kernel/qos.c b/arch/riscv/kernel/qos.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..ea33201a43f61534bf28b9c02b62801f30f62154
> --- /dev/null
> +++ b/arch/riscv/kernel/qos.c
> @@ -0,0 +1,98 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +#include <linux/cpu.h>
> +#include <linux/cpu_pm.h>
> +#include <linux/cpuhotplug.h>
> +#include <linux/notifier.h>
> +#include <linux/percpu-defs.h>
> +#include <linux/types.h>
> +
> +#include <asm/cpufeature-macros.h>
> +#include <asm/hwcap.h>
> +#include <asm/qos.h>
> +
> +/*
> + * Cached value of srmcfg csr for each cpu. Seeded to U32_MAX so the next
> + * __switch_to_srmcfg() unconditionally writes the CSR. The encoding
> + * MCID << 16 | RCID with both fields well under 16 bits can never
> + * produce this sentinel. This covers early-boot context switches that
> + * happen before riscv_srmcfg_init() runs as an arch_initcall.
> + */
> +DEFINE_PER_CPU(u32, cpu_srmcfg) = U32_MAX;
> +
> +/* default srmcfg value for each cpu, set via resctrl cpu assignment */
> +DEFINE_PER_CPU(u32, cpu_srmcfg_default);
> +
> +/*
> + * Invalidate the per-CPU srmcfg cache, used as both the cpuhp startup and
> + * teardown callback. The sentinel is a value no real srmcfg encoding can
> + * produce (MCID << 16 | RCID, both fields well under 16 bits) so the next
> + * __switch_to_srmcfg() unconditionally writes the CSR.
> + *
> + * Ssqosid v1.0 leaves CSR state across hart stop/start implementation-
> + * defined, so the cached value cannot be trusted after online. Invalidating
> + * on offline as well means the sentinel persists across the offline period:
> + * a CPU brought back online finds the cache already invalidated before it is
> + * schedulable, closing the window where a task scheduled before the startup
> + * callback runs could match a stale cache and skip the CSR write while the
> + * hardware CSR was reset across hart stop/start.
> + */
> +static int riscv_srmcfg_reset_cache(unsigned int cpu)
> +{
> + per_cpu(cpu_srmcfg, cpu) = U32_MAX;
> + return 0;
> +}
> +
> +/*
> + * CPU PM notifier: invalidate the cached srmcfg on resume from a deep
> + * idle / suspend. Ssqosid v1.0 leaves CSR_SRMCFG state across low-power
> + * transitions implementation-defined, and the boot CPU never goes
> + * through the cpuhp online callback during system suspend, so without
> + * this hook __switch_to_srmcfg() would skip the CSR write when the
> + * outgoing task happens to share its srmcfg with the pre-suspend cache.
> + */
> +static int riscv_srmcfg_pm_notify(struct notifier_block *nb,
> + unsigned long action, void *unused)
> +{
> + switch (action) {
> + case CPU_PM_EXIT:
> + case CPU_PM_ENTER_FAILED:
> + /*
> + * The CSR is implementation-defined across the low-power
> + * transition. Invalidate the cache and eagerly rewrite the
> + * CSR for the current task so it does not run mis-tagged
> + * until the next context switch.
> + */
> + __this_cpu_write(cpu_srmcfg, U32_MAX);
> + __switch_to_srmcfg(current);
> + break;
> + }
> + return NOTIFY_OK;
> +}
> +
> +static struct notifier_block riscv_srmcfg_pm_nb = {
> + .notifier_call = riscv_srmcfg_pm_notify,
> +};
> +
> +static int __init riscv_srmcfg_init(void)
> +{
> + int err;
> +
> + if (!riscv_has_extension_unlikely(RISCV_ISA_EXT_SSQOSID))
> + return 0;
> +
> + /*
> + * cpuhp_setup_state() invokes the startup callback locally on every
> + * already-online CPU, so no separate seed loop is needed here.
> + */
> + err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "riscv/srmcfg:online",
> + riscv_srmcfg_reset_cache, riscv_srmcfg_reset_cache);
> + if (err < 0) {
> + pr_warn("srmcfg cpuhp registration failed (%d), cpus brought online after boot will not invalidate the CSR_SRMCFG cache\n",
> + err);
> + return err;
> + }
> +
> + cpu_pm_register_notifier(&riscv_srmcfg_pm_nb);
> + return 0;
> +}
> +arch_initcall(riscv_srmcfg_init);
>
> --
> 2.34.1
>
Thanks,
Yunhui
^ permalink raw reply
* Re: [External] [PATCH v2 4/8] riscv_cbqri: Add capacity controller probe and allocation device ops
From: yunhui cui @ 2026-06-27 9:19 UTC (permalink / raw)
To: Drew Fustini
Cc: Adrien Ricciardi, Alexandre Ghiti, Atish Kumar Patra, Atish Patra,
Babu Moger, Ben Horgan, Borislav Petkov, Chen Pei, Conor Dooley,
Conor Dooley, Dave Hansen, Dave Martin, Fenghua Yu, Gong Shuai,
Gong Shuai, guo.wenjia23, James Morse, Kornel Dulęba,
Krzysztof Kozlowski, liu.qingtao2, Liu Zhiwei, Palmer Dabbelt,
Paul Walmsley, Peter Newman, Radim Krčmář,
Reinette Chatre, Rob Herring, Samuel Holland,
Sebastian Andrzej Siewior, Tony Luck, Vasudevan Srinivasan,
Ved Shanbhogue, Weiwei Li, linux-kernel, linux-riscv, x86,
devicetree, linux-rt-devel, linux-doc
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-4-2f8049fd902b@kernel.org>
Hi Drew,
On Thu, Jun 25, 2026 at 9:41 AM Drew Fustini <fustini@kernel.org> wrote:
>
> Add support for the RISC-V CBQRI capacity controller. A platform driver
> passes a cbqri_controller_info descriptor together with the cache level
> to riscv_cbqri_register_cc_dt(), which probes the controller and adds it
> to the controller list.
>
> Assisted-by: Claude:claude-opus-4-7
> Co-developed-by: Adrien Ricciardi <aricciardi@baylibre.com>
> Signed-off-by: Adrien Ricciardi <aricciardi@baylibre.com>
> Signed-off-by: Drew Fustini <fustini@kernel.org>
> ---
> MAINTAINERS | 3 +
> drivers/resctrl/Kconfig | 13 +
> drivers/resctrl/Makefile | 3 +
> drivers/resctrl/cbqri_devices.c | 520 +++++++++++++++++++++++++++++++++++++++
> drivers/resctrl/cbqri_internal.h | 107 ++++++++
> include/linux/riscv_cbqri.h | 47 ++++
> 6 files changed, 693 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 07109e1a8f8470377916c98074ab68fec51dfdc6..811c0c9b1fac806945cad0229c5330654420a835 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -23300,6 +23300,9 @@ L: linux-riscv@lists.infradead.org
> S: Supported
> F: arch/riscv/include/asm/qos.h
> F: arch/riscv/kernel/qos.c
> +F: drivers/resctrl/cbqri_devices.c
> +F: drivers/resctrl/cbqri_internal.h
> +F: include/linux/riscv_cbqri.h
>
> RISC-V RPMI AND MPXY DRIVERS
> M: Rahul Pathak <rahul@summations.net>
> diff --git a/drivers/resctrl/Kconfig b/drivers/resctrl/Kconfig
> index 672abea3b03ccbeb4532832dd18e5cb80e90ff5b..92b9c82cf9f397437d28006e79d40c2d9b384eb0 100644
> --- a/drivers/resctrl/Kconfig
> +++ b/drivers/resctrl/Kconfig
> @@ -29,3 +29,16 @@ config ARM64_MPAM_RESCTRL_FS
> default y if ARM64_MPAM_DRIVER && RESCTRL_FS
> select RESCTRL_RMID_DEPENDS_ON_CLOSID
> select RESCTRL_ASSIGN_FIXED
> +
> +menuconfig RISCV_CBQRI
> + bool "RISC-V CBQRI support"
> + depends on RISCV && RISCV_ISA_SSQOSID
> + help
> + Capacity and Bandwidth QoS Register Interface (CBQRI) support for
> + RISC-V cache QoS resources. CBQRI exposes cache capacity
> + allocation through the resctrl filesystem at /sys/fs/resctrl when
> + RESCTRL_FS is also enabled.
> +
> +if RISCV_CBQRI
> +
> +endif
> diff --git a/drivers/resctrl/Makefile b/drivers/resctrl/Makefile
> index 4f6d0e81f9b8f34bd8842fdb13be69da72515b79..4d8a2c4b5627144a651da007174d4d9df6171330 100644
> --- a/drivers/resctrl/Makefile
> +++ b/drivers/resctrl/Makefile
> @@ -3,3 +3,6 @@ mpam-y += mpam_devices.o
> mpam-$(CONFIG_ARM64_MPAM_RESCTRL_FS) += mpam_resctrl.o
>
> ccflags-$(CONFIG_ARM64_MPAM_DRIVER_DEBUG) += -DDEBUG
> +
> +obj-$(CONFIG_RISCV_CBQRI) += cbqri.o
> +cbqri-y += cbqri_devices.o
> diff --git a/drivers/resctrl/cbqri_devices.c b/drivers/resctrl/cbqri_devices.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..8ad9df404f65d5d82722cf8b78f02936c489ca6d
> --- /dev/null
> +++ b/drivers/resctrl/cbqri_devices.c
> @@ -0,0 +1,520 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
> +
> +#include <linux/bitfield.h>
> +#include <linux/riscv_cbqri.h>
> +#include <linux/cpumask.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/iopoll.h>
> +#include <linux/ioport.h>
> +#include <linux/list.h>
> +#include <linux/mutex.h>
> +#include <linux/printk.h>
> +#include <linux/slab.h>
> +#include <linux/types.h>
> +
> +#include <asm/csr.h>
> +
> +#include "cbqri_internal.h"
> +
> +LIST_HEAD(cbqri_controllers);
> +
> +/*
> + * Serializes cbqri_controllers mutations against a concurrent insert under
> + * asynchronous driver probing, and against the boot-time walk in the resctrl
> + * glue. Runtime cpuhp walks happen after registration has settled.
> + */
> +DEFINE_MUTEX(cbqri_controllers_lock);
> +
> +/* Set capacity block mask (cc_block_mask) */
> +static void cbqri_set_cbm(struct cbqri_controller *ctrl, u64 cbm)
> +{
> + iowrite64(cbm, ctrl->base + CBQRI_CC_BLOCK_MASK_OFF);
> +}
> +
> +static int cbqri_wait_busy_flag(struct cbqri_controller *ctrl, int reg_offset,
> + u64 *regp)
> +{
> + u64 reg;
> + int ret;
> +
> + /*
> + * Sleeping poll: caller holds ctrl->lock as a sleeping mutex, so
> + * 10us/1ms is safe under PREEMPT_RT.
> + */
> + ret = readq_poll_timeout(ctrl->base + reg_offset, reg,
> + !FIELD_GET(CBQRI_CONTROL_REGISTERS_BUSY_MASK, reg),
> + 10, 1000);
> + if (ret)
> + return ret;
> + if (regp)
> + *regp = reg;
> + return 0;
> +}
> +
> +/*
> + * Perform capacity allocation control operation on capacity controller.
> + * Caller must hold ctrl->lock.
> + */
> +static int cbqri_cc_alloc_op(struct cbqri_controller *ctrl, int operation,
> + int rcid, u32 at)
> +{
> + int reg_offset = CBQRI_CC_ALLOC_CTL_OFF;
> + int status;
> + u64 reg;
> +
> + lockdep_assert_held(&ctrl->lock);
> +
> + if (cbqri_wait_busy_flag(ctrl, reg_offset, ®) < 0) {
> + pr_err_ratelimited("BUSY timeout before starting operation\n");
> + return -EIO;
> + }
> + FIELD_MODIFY(CBQRI_CONTROL_REGISTERS_OP_MASK, ®, operation);
> + FIELD_MODIFY(CBQRI_CONTROL_REGISTERS_RCID_MASK, ®, rcid);
> +
> + /*
> + * CBQRI Table 1: AT 0=Data, 1=Code. Program AT on controllers
> + * that report supports_alloc_at_code. On controllers that don't,
> + * AT is reserved-zero and the op acts on both halves.
> + */
> + reg &= ~CBQRI_CONTROL_REGISTERS_AT_MASK;
> + if (ctrl->cc.supports_alloc_at_code)
> + reg |= FIELD_PREP(CBQRI_CONTROL_REGISTERS_AT_MASK, at);
> +
> + iowrite64(reg, ctrl->base + reg_offset);
> +
> + if (cbqri_wait_busy_flag(ctrl, reg_offset, ®) < 0) {
> + pr_err_ratelimited("BUSY timeout during operation\n");
> + return -EIO;
> + }
> +
> + status = FIELD_GET(CBQRI_CONTROL_REGISTERS_STATUS_MASK, reg);
> + if (status != CBQRI_CC_ALLOC_CTL_STATUS_SUCCESS) {
> + pr_err_ratelimited("operation %d failed: status=%d\n", operation, status);
> + return -EIO;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Apply a capacity block mask and verify via CONFIG_LIMIT + READ_LIMIT.
> + *
> + * AT-capable controllers with CDP off need a second CONFIG_LIMIT on the
> + * other AT half (the spec encodes AT only as 0=Data / 1=Code, there is
> + * no "both halves" value). CDP-on issues separate per-type writes from
> + * resctrl, so a single CONFIG_LIMIT per call is correct.
> + */
> +int cbqri_apply_cache_config(struct cbqri_controller *ctrl, u32 closid,
> + const struct cbqri_cc_config *cfg)
> +{
> + bool need_at_mirror;
> + u64 saved_cbm = 0;
> + int err = 0;
> + u64 reg;
> +
> + mutex_lock(&ctrl->lock);
> +
> + need_at_mirror = ctrl->cc.supports_alloc_at_code && !cfg->cdp_enabled;
> +
> + /*
> + * Capture the cfg->at half CBM before any write so a partial
> + * AT-mirror failure can revert and keep the two halves consistent.
> + * Pre-clear cc_block_mask so a silent firmware no-op (status
> + * SUCCESS but staging not updated) shows as a zero readback
> + * rather than carrying stale data from a prior op.
> + */
> + if (need_at_mirror) {
> + cbqri_set_cbm(ctrl, 0);
> + err = cbqri_cc_alloc_op(ctrl, CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT,
> + closid, cfg->at);
> + if (err < 0)
> + goto out;
> + saved_cbm = ioread64(ctrl->base + CBQRI_CC_BLOCK_MASK_OFF);
> + }
> +
> + /* Set capacity block mask (cc_block_mask) */
> + cbqri_set_cbm(ctrl, cfg->cbm);
> +
> + /* Capacity config limit operation for the AT half implied by cfg->at */
> + err = cbqri_cc_alloc_op(ctrl, CBQRI_CC_ALLOC_CTL_OP_CONFIG_LIMIT,
> + closid, cfg->at);
> + if (err < 0)
> + goto out;
When CUNITS=1, CONFIG_LIMIT also consumes cc_cunits. If resctrl does not
expose unit limits, the driver should still write cc_cunits=0 before
CONFIG_LIMIT to avoid a hidden stale/implementation-defined unit limit.
Should we handle cc_cunits here?
> +
> + /*
> + * CDP-off mirror: on AT-capable controllers, also program the
> + * other AT half with the same mask so the two halves stay in sync.
> + */
> + if (need_at_mirror) {
> + u32 other = (cfg->at == CBQRI_CONTROL_REGISTERS_AT_CODE) ?
> + CBQRI_CONTROL_REGISTERS_AT_DATA :
> + CBQRI_CONTROL_REGISTERS_AT_CODE;
> +
> + cbqri_set_cbm(ctrl, cfg->cbm);
> + err = cbqri_cc_alloc_op(ctrl,
> + CBQRI_CC_ALLOC_CTL_OP_CONFIG_LIMIT,
> + closid, other);
> + if (err < 0) {
> + int rerr;
> +
> + /*
> + * Best-effort revert of the cfg->at half so the two
> + * halves stay in sync. A schemata read sees only one
> + * half, so silent divergence would otherwise report
> + * the new value as if the write had succeeded.
> + */
> + cbqri_set_cbm(ctrl, saved_cbm);
> + rerr = cbqri_cc_alloc_op(ctrl,
> + CBQRI_CC_ALLOC_CTL_OP_CONFIG_LIMIT,
> + closid, cfg->at);
> + if (rerr < 0)
> + pr_err_ratelimited("AT-mirror revert failed (err=%d), AT halves diverged\n",
> + rerr);
> + goto out;
> + }
> + }
> +
> + /* Clear cc_block_mask before read limit to verify op works */
> + cbqri_set_cbm(ctrl, 0);
> +
> + /* Perform a capacity read limit operation to verify blockmask */
> + err = cbqri_cc_alloc_op(ctrl, CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT,
> + closid, cfg->at);
> + if (err < 0)
> + goto out;
> +
> + /*
> + * Read capacity blockmask and narrow to u32 to match resctrl's CBM
> + * width. cbqri_probe_cc() rejects ncblks > 32 so the upper bits are
> + * reserved zero.
> + */
> + reg = ioread64(ctrl->base + CBQRI_CC_BLOCK_MASK_OFF);
> + if (lower_32_bits(reg) != cfg->cbm) {
> + pr_err_ratelimited("CBM verify mismatch (reg=%llx != cbm=%llx)\n",
> + reg, cfg->cbm);
> + err = -EIO;
> + }
> +
> +out:
> + mutex_unlock(&ctrl->lock);
> + return err;
> +}
> +
> +/*
> + * Read the configured CBM for closid on the at half via READ_LIMIT.
> + * Pre-clears cc_block_mask before the op so a silent firmware no-op
> + * (status SUCCESS but staging not updated) is detectable in cbm_out.
> + */
> +int cbqri_read_cache_config(struct cbqri_controller *ctrl, u32 closid,
> + u32 at, u32 *cbm_out)
> +{
> + int err;
> +
> + mutex_lock(&ctrl->lock);
> + cbqri_set_cbm(ctrl, 0);
> + err = cbqri_cc_alloc_op(ctrl, CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT, closid, at);
> + if (err == 0) {
> + /*
> + * cc_block_mask is a 64-bit MMIO register. resctrl exposes the
> + * CBM as a u32. cbqri_probe_cc() rejects ncblks > 32 so the
> + * upper 32 bits are reserved zero by the spec. Narrow
> + * explicitly via lower_32_bits() so the assumption is visible
> + * at the read site.
> + */
> + *cbm_out = lower_32_bits(ioread64(ctrl->base + CBQRI_CC_BLOCK_MASK_OFF));
> + }
> + mutex_unlock(&ctrl->lock);
> + return err;
> +}
> +
> +static int cbqri_probe_feature(struct cbqri_controller *ctrl, int reg_offset,
> + int operation, int *status, bool *access_type_supported)
> +{
> + const u64 active_mask = CBQRI_CONTROL_REGISTERS_OP_MASK |
> + CBQRI_CONTROL_REGISTERS_AT_MASK |
> + CBQRI_CONTROL_REGISTERS_RCID_MASK;
> + u64 reg, saved_reg;
> + int at;
> +
> + /*
> + * Default the output to false so the status==0 (feature not
> + * implemented) path returns a deterministic value to the caller
> + * rather than leaving an uninitialized bool.
> + */
> + *access_type_supported = false;
> +
> + /* Keep the initial register value to preserve the WPRI fields */
> + reg = ioread64(ctrl->base + reg_offset);
> + saved_reg = reg;
> +
> + /* Drain any in-flight firmware op before issuing our own write. */
> + if (cbqri_wait_busy_flag(ctrl, reg_offset, &saved_reg) < 0) {
> + pr_err("BUSY timeout before probe operation\n");
> + return -EIO;
> + }
> +
> + /*
> + * Execute the requested operation with all active fields
> + * (OP/AT/RCID) zeroed except OP itself. Every bit not in
> + * active_mask is WPRI and gets carried over from saved_reg.
> + */
> + reg = (saved_reg & ~active_mask) |
> + FIELD_PREP(CBQRI_CONTROL_REGISTERS_OP_MASK, operation);
> + iowrite64(reg, ctrl->base + reg_offset);
> + if (cbqri_wait_busy_flag(ctrl, reg_offset, ®) < 0) {
> + pr_err_ratelimited("BUSY timeout during operation\n");
> + return -EIO;
> + }
> +
> + /* Get the operation status */
> + *status = FIELD_GET(CBQRI_CONTROL_REGISTERS_STATUS_MASK, reg);
> +
> + /*
> + * Check for the AT support if the register is implemented
> + * (if not, the status value will remain 0)
> + */
> + if (*status != 0) {
> + /*
> + * Re-issue operation with AT=CODE so the controller
> + * latches AT=CODE on supported hardware (or resets it to 0
> + * on hardware that doesn't). OP must be a defined CBQRI op
> + * here. OP=0 is a no-op and would silently disable CDP.
> + */
> + reg = (saved_reg & ~active_mask) |
> + FIELD_PREP(CBQRI_CONTROL_REGISTERS_OP_MASK, operation) |
> + FIELD_PREP(CBQRI_CONTROL_REGISTERS_AT_MASK,
> + CBQRI_CONTROL_REGISTERS_AT_CODE);
> + iowrite64(reg, ctrl->base + reg_offset);
> + if (cbqri_wait_busy_flag(ctrl, reg_offset, ®) < 0) {
> + pr_err("BUSY timeout setting AT field\n");
> + return -EIO;
> + }
> +
> + /*
> + * If the AT field value has been reset to zero,
> + * then the AT support is not present
> + */
> + at = FIELD_GET(CBQRI_CONTROL_REGISTERS_AT_MASK, reg);
> + if (at == CBQRI_CONTROL_REGISTERS_AT_CODE)
> + *access_type_supported = true;
> + }
> +
> + /*
> + * Restore the original register value.
> + * Clear OP to avoid re-triggering the probe op.
> + */
> + saved_reg &= ~CBQRI_CONTROL_REGISTERS_OP_MASK;
> + iowrite64(saved_reg, ctrl->base + reg_offset);
> + if (cbqri_wait_busy_flag(ctrl, reg_offset, NULL) < 0) {
> + pr_err("BUSY timeout restoring register value\n");
> + return -EIO;
> + }
> +
> + return 0;
> +}
> +
> +static int cbqri_probe_cc(struct cbqri_controller *ctrl)
> +{
> + int err, status;
> + int ver_major, ver_minor;
> + u64 reg;
> +
> + reg = ioread64(ctrl->base + CBQRI_CC_CAPABILITIES_OFF);
> + if (reg == 0)
> + return -ENODEV;
> +
> + ver_minor = FIELD_GET(CBQRI_CC_CAPABILITIES_VER_MINOR_MASK, reg);
> + ver_major = FIELD_GET(CBQRI_CC_CAPABILITIES_VER_MAJOR_MASK, reg);
> + ctrl->cc.ncblks = FIELD_GET(CBQRI_CC_CAPABILITIES_NCBLKS_MASK, reg);
> +
> + pr_debug("version=%d.%d ncblks=%d cache_level=%d\n",
> + ver_major, ver_minor,
> + ctrl->cc.ncblks, ctrl->cache.cache_level);
> +
> + /*
> + * NCBLKS == 0 would divide-by-zero in the schemata math while
> + * ctrl->lock is held.
> + */
> + if (!ctrl->cc.ncblks) {
> + pr_warn("CC at %pa has 0 capacity blocks, skipping\n",
> + &ctrl->addr);
> + return -ENODEV;
> + }
> +
> + if (ctrl->cc.ncblks > 32) {
> + pr_warn("CC at %pa has ncblks=%u > 32 (resctrl CBM is u32), skipping\n",
> + &ctrl->addr, ctrl->cc.ncblks);
> + return -ENODEV;
> + }
> +
> + /* Probe allocation features */
> + err = cbqri_probe_feature(ctrl, CBQRI_CC_ALLOC_CTL_OFF,
> + CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT,
> + &status, &ctrl->cc.supports_alloc_at_code);
> + if (err)
> + return err;
> +
> + if (status == CBQRI_CC_ALLOC_CTL_STATUS_SUCCESS)
> + ctrl->alloc_capable = true;
> +
> + return 0;
> +}
> +
> +static int cbqri_probe_controller(struct cbqri_controller *ctrl)
> +{
> + int err;
> +
> + pr_debug("controller info: type=%d addr=%pa size=%pa max-rcid=%u\n",
> + ctrl->type, &ctrl->addr, &ctrl->size, ctrl->rcid_count);
> +
> + if (!ctrl->addr) {
> + pr_warn("controller has invalid addr=0x0, skipping\n");
> + return -EINVAL;
> + }
> +
> + if (ctrl->size < CBQRI_CTRL_MIN_REG_SPAN) {
> + pr_warn("controller at %pa: size %pa < minimum 0x%x, skipping\n",
> + &ctrl->addr, &ctrl->size, CBQRI_CTRL_MIN_REG_SPAN);
> + return -EINVAL;
> + }
> +
> + if (!request_mem_region(ctrl->addr, ctrl->size, "cbqri_controller")) {
> + pr_err("request_mem_region failed for %pa\n", &ctrl->addr);
> + return -EBUSY;
> + }
> +
> + ctrl->base = ioremap(ctrl->addr, ctrl->size);
> + if (!ctrl->base) {
> + pr_err("ioremap failed for %pa\n", &ctrl->addr);
> + err = -ENOMEM;
> + goto err_release;
> + }
> +
> + switch (ctrl->type) {
> + case CBQRI_CONTROLLER_TYPE_CAPACITY:
> + err = cbqri_probe_cc(ctrl);
> + break;
> + default:
> + pr_err("unknown controller type %d\n", ctrl->type);
> + err = -ENODEV;
> + break;
> + }
> +
> + if (err)
> + goto err_iounmap;
> +
> + return 0;
> +
> +err_iounmap:
> + iounmap(ctrl->base);
> + ctrl->base = NULL;
> +err_release:
> + release_mem_region(ctrl->addr, ctrl->size);
> + return err;
> +}
> +
> +void cbqri_controller_destroy(struct cbqri_controller *ctrl)
> +{
> + /*
> + * cbqri_probe_controller() clears ctrl->base on its error paths and
> + * releases the mem region itself, so reach into both only when
> + * destroy is rolling back a successful probe.
> + */
> + if (ctrl->base) {
> + iounmap(ctrl->base);
> + release_mem_region(ctrl->addr, ctrl->size);
> + }
> + kfree(ctrl);
> +}
> +
> +/**
> + * riscv_cbqri_register_cc_dt() - register a DT-described capacity controller
> + * @info: registration descriptor. info->cache_id is used as the
> + * resctrl domain id. info->type must be CAPACITY.
> + * @cache_level: cache level (2 or 3) the controller backs, mapped to the
> + * resctrl L2/L3 resource by the resctrl glue.
> + * @cpu_mask: CPUs that share this cache.
> + *
> + * The cache topology is supplied directly by the caller. A device-tree
> + * platform driver that already knows which CPUs share the cache and at what
> + * level passes that in. There is no firmware table to resolve it from.
> + *
> + * Return: 0 on success, or a negative errno on failure.
> + */
> +int riscv_cbqri_register_cc_dt(const struct cbqri_controller_info *info,
> + u32 cache_level, const struct cpumask *cpu_mask)
> +{
> + struct cbqri_controller *ctrl;
> + int err;
> +
> + if (!info->addr) {
> + pr_warn("skipping controller with invalid addr=0x0\n");
> + return -EINVAL;
> + }
> +
> + if (info->type != CBQRI_CONTROLLER_TYPE_CAPACITY) {
> + pr_warn("register_cc_dt called with non-capacity type %u\n",
> + info->type);
> + return -EINVAL;
> + }
> +
> + if (!cpu_mask || cpumask_empty(cpu_mask)) {
> + pr_warn("register_cc_dt called with empty cpu_mask\n");
> + return -EINVAL;
> + }
> +
> + ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL);
> + if (!ctrl)
> + return -ENOMEM;
> +
> + mutex_init(&ctrl->lock);
> +
> + ctrl->addr = info->addr;
> + ctrl->size = info->size;
> + ctrl->type = info->type;
> + ctrl->rcid_count = info->rcid_count;
> +
> + /*
> + * SRMCFG encodes RCID in 12 bits. Reject an out-of-range count rather
> + * than silently truncating in every FIELD_PREP(SRMCFG_RCID_MASK, closid)
> + * on the schedule-in fast path.
> + */
> + if (ctrl->rcid_count > FIELD_MAX(SRMCFG_RCID_MASK) + 1) {
> + pr_warn("CC at %pa has RCID count %u beyond the 12-bit SRMCFG field, skipping\n",
> + &ctrl->addr, ctrl->rcid_count);
> + cbqri_controller_destroy(ctrl);
> + return -EINVAL;
> + }
> +
> + ctrl->cache.cache_id = info->cache_id;
> + ctrl->cache.cache_level = cache_level;
> + cpumask_copy(&ctrl->cache.cpu_mask, cpu_mask);
> +
> + err = cbqri_probe_controller(ctrl);
> + if (err) {
> + cbqri_controller_destroy(ctrl);
> + return err;
> + }
> +
> + /*
> + * Allocation capability comes from the capabilities register probed
> + * above, not from device tree. rcid_count only bounds the RCID range,
> + * so a controller the hardware reports as alloc-capable but described
> + * with no RCID count cannot be driven. Reject that inconsistency. A
> + * monitoring-only controller (not alloc_capable) needs no RCID count.
> + */
> + if (ctrl->alloc_capable && !ctrl->rcid_count) {
> + pr_warn("CC at %pa is alloc-capable but has no RCID count, skipping\n",
> + &ctrl->addr);
> + cbqri_controller_destroy(ctrl);
> + return -EINVAL;
> + }
> +
> + mutex_lock(&cbqri_controllers_lock);
> + list_add_tail(&ctrl->list, &cbqri_controllers);
> + mutex_unlock(&cbqri_controllers_lock);
> + return 0;
> +}
> diff --git a/drivers/resctrl/cbqri_internal.h b/drivers/resctrl/cbqri_internal.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..2192a3831bcde7cd4fddf03f2ce1103902e8815c
> --- /dev/null
> +++ b/drivers/resctrl/cbqri_internal.h
> @@ -0,0 +1,107 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +#ifndef _DRIVERS_RESCTRL_CBQRI_INTERNAL_H
> +#define _DRIVERS_RESCTRL_CBQRI_INTERNAL_H
> +
> +#include <linux/bitfield.h>
> +#include <linux/riscv_cbqri.h>
> +#include <linux/cpumask.h>
> +#include <linux/list.h>
> +#include <linux/mutex.h>
> +#include <linux/types.h>
> +
> +/* Capacity Controller (CC) MMIO register offsets. */
> +#define CBQRI_CC_CAPABILITIES_OFF 0
> +#define CBQRI_CC_ALLOC_CTL_OFF 24
> +#define CBQRI_CC_BLOCK_MASK_OFF 32
> +
> +/*
> + * Highest defined register offset (0x20) plus the 8-byte register width.
> + * cbqri_probe_controller() rejects smaller mappings so a u64 access at
> + * BLOCK_MASK stays in bounds.
> + */
> +#define CBQRI_CTRL_MIN_REG_SPAN 0x28u
> +
> +#define CBQRI_CC_CAPABILITIES_VER_MINOR_MASK GENMASK_ULL(3, 0)
> +#define CBQRI_CC_CAPABILITIES_VER_MAJOR_MASK GENMASK_ULL(7, 4)
> +#define CBQRI_CC_CAPABILITIES_NCBLKS_MASK GENMASK_ULL(23, 8)
> +
> +/*
> + * CC control registers are 64-bit. Keep every field mask GENMASK_ULL so
> + * FIELD_MODIFY() or ~mask on a u64 register never zero-extends a 32-bit
> + * mask and clobbers STATUS/BUSY/WPRI in bits 63:32 if RV32 support is
> + * added in the future.
> + */
> +#define CBQRI_CONTROL_REGISTERS_OP_MASK GENMASK_ULL(4, 0)
> +#define CBQRI_CONTROL_REGISTERS_AT_MASK GENMASK_ULL(7, 5)
> +/* AT field values (CBQRI Table 1): data vs code half for CDP */
> +#define CBQRI_CONTROL_REGISTERS_AT_DATA 0
> +#define CBQRI_CONTROL_REGISTERS_AT_CODE 1
> +#define CBQRI_CONTROL_REGISTERS_RCID_MASK GENMASK_ULL(19, 8)
> +#define CBQRI_CONTROL_REGISTERS_STATUS_MASK GENMASK_ULL(38, 32)
> +#define CBQRI_CONTROL_REGISTERS_BUSY_MASK GENMASK_ULL(39, 39)
> +
> +#define CBQRI_CC_ALLOC_CTL_OP_CONFIG_LIMIT 1
> +#define CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT 2
> +#define CBQRI_CC_ALLOC_CTL_STATUS_SUCCESS 1
> +
> +/* Capacity Controller hardware capabilities */
> +struct riscv_cbqri_capacity_caps {
> + u16 ncblks;
> + bool supports_alloc_at_code;
> +};
> +
> +/**
> + * struct cbqri_cc_config - desired capacity allocation state for one rcid
> + * @cbm: capacity block mask
> + * @at: AT half the @cbm applies to (CBQRI_CONTROL_REGISTERS_AT_DATA
> + * or CBQRI_CONTROL_REGISTERS_AT_CODE)
> + * @cdp_enabled: when false and the controller supports AT, mirror @cbm
> + * into the other AT half so both stay in sync
> + */
> +struct cbqri_cc_config {
> + u64 cbm;
> + u32 at;
> + bool cdp_enabled;
> +};
> +
> +struct cbqri_controller {
> + void __iomem *base;
> + /*
> + * Serializes the write-then-poll-busy MMIO sequences on this
> + * controller. Each CBQRI op may busy-wait up to 1 ms on slow
> + * firmware, so use a sleeping mutex to keep preemption enabled.
> + * All resctrl-arch entry points run in process context.
> + */
> + struct mutex lock;
> +
> + struct riscv_cbqri_capacity_caps cc;
> +
> + bool alloc_capable;
> +
> + phys_addr_t addr;
> + phys_addr_t size;
> + enum cbqri_controller_type type;
> + u32 rcid_count;
> +
> + struct list_head list;
> +
> + struct cache_controller {
> + u32 cache_level;
> + struct cpumask cpu_mask;
> + /* Cache id used as the resctrl domain id */
> + u32 cache_id;
> + } cache;
> +};
> +
> +extern struct list_head cbqri_controllers;
> +extern struct mutex cbqri_controllers_lock;
> +
> +void cbqri_controller_destroy(struct cbqri_controller *ctrl);
> +
> +int cbqri_apply_cache_config(struct cbqri_controller *ctrl, u32 closid,
> + const struct cbqri_cc_config *cfg);
> +
> +int cbqri_read_cache_config(struct cbqri_controller *ctrl, u32 closid,
> + u32 at, u32 *cbm_out);
> +
> +#endif /* _DRIVERS_RESCTRL_CBQRI_INTERNAL_H */
> diff --git a/include/linux/riscv_cbqri.h b/include/linux/riscv_cbqri.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..58737224d2f2c67a443f78f9fea7f368c7d16044
> --- /dev/null
> +++ b/include/linux/riscv_cbqri.h
> @@ -0,0 +1,47 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Public registration API for the RISC-V Capacity and Bandwidth QoS
> + * Register Interface (CBQRI) core. Discovery layers (device tree
> + * platform drivers) call riscv_cbqri_register_cc_dt() to hand a capacity
> + * controller descriptor to the core, which owns all subsequent state.
> + */
> +#ifndef _LINUX_RISCV_CBQRI_H
> +#define _LINUX_RISCV_CBQRI_H
> +
> +#include <linux/types.h>
> +
> +struct cpumask;
> +
> +enum cbqri_controller_type {
> + CBQRI_CONTROLLER_TYPE_CAPACITY,
> +};
> +
> +/**
> + * struct cbqri_controller_info - registration descriptor
> + * @addr: MMIO base address of the controller's register interface
> + * @size: size of the MMIO region
> + * @type: controller type (capacity)
> + * @rcid_count: number of supported RCIDs
> + * @cache_id: cache id used as the resctrl domain id
> + */
> +struct cbqri_controller_info {
> + phys_addr_t addr;
> + phys_addr_t size;
> + enum cbqri_controller_type type;
> + u32 rcid_count;
> + u32 cache_id;
> +};
> +
> +#if IS_ENABLED(CONFIG_RISCV_CBQRI)
> +int riscv_cbqri_register_cc_dt(const struct cbqri_controller_info *info,
> + u32 cache_level, const struct cpumask *cpu_mask);
> +#else
> +static inline int
> +riscv_cbqri_register_cc_dt(const struct cbqri_controller_info *info,
> + u32 cache_level, const struct cpumask *cpu_mask)
> +{
> + return -ENODEV;
> +}
> +#endif
> +
> +#endif /* _LINUX_RISCV_CBQRI_H */
>
> --
> 2.34.1
>
Thanks,
Yunhui
^ permalink raw reply
* [PATCH] dt-bindings: Fix bracket
From: Manuel Ebner @ 2026-06-27 9:19 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Oleksij Rempel, open list:NETWORKING DRIVERS,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
open list, Randy Dunlap
Cc: Manuel Ebner
Add "(Alternate" to "ID)"
Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
---
Documentation/devicetree/bindings/net/microchip,lan95xx.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/net/microchip,lan95xx.yaml b/Documentation/devicetree/bindings/net/microchip,lan95xx.yaml
index accff93d38f8..62bf982aff2b 100644
--- a/Documentation/devicetree/bindings/net/microchip,lan95xx.yaml
+++ b/Documentation/devicetree/bindings/net/microchip,lan95xx.yaml
@@ -33,7 +33,7 @@ properties:
- usb424,9906 # SMSC9505A USB Ethernet Device (HAL)
- usb424,9907 # SMSC9500 USB Ethernet Device (Alternate ID)
- usb424,9908 # SMSC9500A USB Ethernet Device (Alternate ID)
- - usb424,9909 # SMSC9512/9514 USB Hub & Ethernet Device ID)
+ - usb424,9909 # SMSC9512/9514 USB Hub & Ethernet Device (Alternate ID)
- usb424,9e00 # SMSC9500A USB Ethernet Device
- usb424,9e01 # SMSC9505A USB Ethernet Device
- usb424,9e08 # SMSC LAN89530 USB Ethernet Device
--
2.54.0
^ permalink raw reply related
* Re: [External] [PATCH v2 4/8] riscv_cbqri: Add capacity controller probe and allocation device ops
From: yunhui cui @ 2026-06-27 9:31 UTC (permalink / raw)
To: Drew Fustini
Cc: Adrien Ricciardi, Alexandre Ghiti, Atish Kumar Patra, Atish Patra,
Babu Moger, Ben Horgan, Borislav Petkov, Chen Pei, Conor Dooley,
Conor Dooley, Dave Hansen, Dave Martin, Fenghua Yu, Gong Shuai,
Gong Shuai, guo.wenjia23, James Morse, Kornel Dulęba,
Krzysztof Kozlowski, liu.qingtao2, Liu Zhiwei, Palmer Dabbelt,
Paul Walmsley, Peter Newman, Radim Krčmář,
Reinette Chatre, Rob Herring, Samuel Holland,
Sebastian Andrzej Siewior, Tony Luck, Vasudevan Srinivasan,
Ved Shanbhogue, Weiwei Li, linux-kernel, linux-riscv, x86,
devicetree, linux-rt-devel, linux-doc
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-4-2f8049fd902b@kernel.org>
Hi Drew,
On Thu, Jun 25, 2026 at 9:41 AM Drew Fustini <fustini@kernel.org> wrote:
>
> Add support for the RISC-V CBQRI capacity controller. A platform driver
> passes a cbqri_controller_info descriptor together with the cache level
> to riscv_cbqri_register_cc_dt(), which probes the controller and adds it
> to the controller list.
>
> Assisted-by: Claude:claude-opus-4-7
> Co-developed-by: Adrien Ricciardi <aricciardi@baylibre.com>
> Signed-off-by: Adrien Ricciardi <aricciardi@baylibre.com>
> Signed-off-by: Drew Fustini <fustini@kernel.org>
> ---
> MAINTAINERS | 3 +
> drivers/resctrl/Kconfig | 13 +
> drivers/resctrl/Makefile | 3 +
> drivers/resctrl/cbqri_devices.c | 520 +++++++++++++++++++++++++++++++++++++++
> drivers/resctrl/cbqri_internal.h | 107 ++++++++
> include/linux/riscv_cbqri.h | 47 ++++
> 6 files changed, 693 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 07109e1a8f8470377916c98074ab68fec51dfdc6..811c0c9b1fac806945cad0229c5330654420a835 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -23300,6 +23300,9 @@ L: linux-riscv@lists.infradead.org
> S: Supported
> F: arch/riscv/include/asm/qos.h
> F: arch/riscv/kernel/qos.c
> +F: drivers/resctrl/cbqri_devices.c
> +F: drivers/resctrl/cbqri_internal.h
> +F: include/linux/riscv_cbqri.h
>
> RISC-V RPMI AND MPXY DRIVERS
> M: Rahul Pathak <rahul@summations.net>
> diff --git a/drivers/resctrl/Kconfig b/drivers/resctrl/Kconfig
> index 672abea3b03ccbeb4532832dd18e5cb80e90ff5b..92b9c82cf9f397437d28006e79d40c2d9b384eb0 100644
> --- a/drivers/resctrl/Kconfig
> +++ b/drivers/resctrl/Kconfig
> @@ -29,3 +29,16 @@ config ARM64_MPAM_RESCTRL_FS
> default y if ARM64_MPAM_DRIVER && RESCTRL_FS
> select RESCTRL_RMID_DEPENDS_ON_CLOSID
> select RESCTRL_ASSIGN_FIXED
> +
> +menuconfig RISCV_CBQRI
> + bool "RISC-V CBQRI support"
> + depends on RISCV && RISCV_ISA_SSQOSID
> + help
> + Capacity and Bandwidth QoS Register Interface (CBQRI) support for
> + RISC-V cache QoS resources. CBQRI exposes cache capacity
> + allocation through the resctrl filesystem at /sys/fs/resctrl when
> + RESCTRL_FS is also enabled.
> +
> +if RISCV_CBQRI
> +
> +endif
> diff --git a/drivers/resctrl/Makefile b/drivers/resctrl/Makefile
> index 4f6d0e81f9b8f34bd8842fdb13be69da72515b79..4d8a2c4b5627144a651da007174d4d9df6171330 100644
> --- a/drivers/resctrl/Makefile
> +++ b/drivers/resctrl/Makefile
> @@ -3,3 +3,6 @@ mpam-y += mpam_devices.o
> mpam-$(CONFIG_ARM64_MPAM_RESCTRL_FS) += mpam_resctrl.o
>
> ccflags-$(CONFIG_ARM64_MPAM_DRIVER_DEBUG) += -DDEBUG
> +
> +obj-$(CONFIG_RISCV_CBQRI) += cbqri.o
> +cbqri-y += cbqri_devices.o
> diff --git a/drivers/resctrl/cbqri_devices.c b/drivers/resctrl/cbqri_devices.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..8ad9df404f65d5d82722cf8b78f02936c489ca6d
> --- /dev/null
> +++ b/drivers/resctrl/cbqri_devices.c
> @@ -0,0 +1,520 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
> +
> +#include <linux/bitfield.h>
> +#include <linux/riscv_cbqri.h>
> +#include <linux/cpumask.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/iopoll.h>
> +#include <linux/ioport.h>
> +#include <linux/list.h>
> +#include <linux/mutex.h>
> +#include <linux/printk.h>
> +#include <linux/slab.h>
> +#include <linux/types.h>
> +
> +#include <asm/csr.h>
> +
> +#include "cbqri_internal.h"
> +
> +LIST_HEAD(cbqri_controllers);
> +
> +/*
> + * Serializes cbqri_controllers mutations against a concurrent insert under
> + * asynchronous driver probing, and against the boot-time walk in the resctrl
> + * glue. Runtime cpuhp walks happen after registration has settled.
> + */
> +DEFINE_MUTEX(cbqri_controllers_lock);
> +
> +/* Set capacity block mask (cc_block_mask) */
> +static void cbqri_set_cbm(struct cbqri_controller *ctrl, u64 cbm)
> +{
> + iowrite64(cbm, ctrl->base + CBQRI_CC_BLOCK_MASK_OFF);
The CBQRI spec allows naturally aligned 4-byte accesses and only guarantees
atomicity for 4-byte accesses; 8-byte atomicity is unspecified.
Would 32-bit split accesses be preferable here instead of relying on
ioread64/iowrite64? This may also make the driver less dependent on native
64-bit MMIO support.
> +}
> +
> +static int cbqri_wait_busy_flag(struct cbqri_controller *ctrl, int reg_offset,
> + u64 *regp)
> +{
> + u64 reg;
> + int ret;
> +
> + /*
> + * Sleeping poll: caller holds ctrl->lock as a sleeping mutex, so
> + * 10us/1ms is safe under PREEMPT_RT.
> + */
> + ret = readq_poll_timeout(ctrl->base + reg_offset, reg,
> + !FIELD_GET(CBQRI_CONTROL_REGISTERS_BUSY_MASK, reg),
> + 10, 1000);
> + if (ret)
> + return ret;
> + if (regp)
> + *regp = reg;
> + return 0;
> +}
> +
> +/*
> + * Perform capacity allocation control operation on capacity controller.
> + * Caller must hold ctrl->lock.
> + */
> +static int cbqri_cc_alloc_op(struct cbqri_controller *ctrl, int operation,
> + int rcid, u32 at)
> +{
> + int reg_offset = CBQRI_CC_ALLOC_CTL_OFF;
> + int status;
> + u64 reg;
> +
> + lockdep_assert_held(&ctrl->lock);
> +
> + if (cbqri_wait_busy_flag(ctrl, reg_offset, ®) < 0) {
> + pr_err_ratelimited("BUSY timeout before starting operation\n");
> + return -EIO;
> + }
> + FIELD_MODIFY(CBQRI_CONTROL_REGISTERS_OP_MASK, ®, operation);
> + FIELD_MODIFY(CBQRI_CONTROL_REGISTERS_RCID_MASK, ®, rcid);
> +
> + /*
> + * CBQRI Table 1: AT 0=Data, 1=Code. Program AT on controllers
> + * that report supports_alloc_at_code. On controllers that don't,
> + * AT is reserved-zero and the op acts on both halves.
> + */
> + reg &= ~CBQRI_CONTROL_REGISTERS_AT_MASK;
> + if (ctrl->cc.supports_alloc_at_code)
> + reg |= FIELD_PREP(CBQRI_CONTROL_REGISTERS_AT_MASK, at);
> +
> + iowrite64(reg, ctrl->base + reg_offset);
> +
> + if (cbqri_wait_busy_flag(ctrl, reg_offset, ®) < 0) {
> + pr_err_ratelimited("BUSY timeout during operation\n");
> + return -EIO;
> + }
> +
> + status = FIELD_GET(CBQRI_CONTROL_REGISTERS_STATUS_MASK, reg);
> + if (status != CBQRI_CC_ALLOC_CTL_STATUS_SUCCESS) {
> + pr_err_ratelimited("operation %d failed: status=%d\n", operation, status);
> + return -EIO;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Apply a capacity block mask and verify via CONFIG_LIMIT + READ_LIMIT.
> + *
> + * AT-capable controllers with CDP off need a second CONFIG_LIMIT on the
> + * other AT half (the spec encodes AT only as 0=Data / 1=Code, there is
> + * no "both halves" value). CDP-on issues separate per-type writes from
> + * resctrl, so a single CONFIG_LIMIT per call is correct.
> + */
> +int cbqri_apply_cache_config(struct cbqri_controller *ctrl, u32 closid,
> + const struct cbqri_cc_config *cfg)
> +{
> + bool need_at_mirror;
> + u64 saved_cbm = 0;
> + int err = 0;
> + u64 reg;
> +
> + mutex_lock(&ctrl->lock);
> +
> + need_at_mirror = ctrl->cc.supports_alloc_at_code && !cfg->cdp_enabled;
> +
> + /*
> + * Capture the cfg->at half CBM before any write so a partial
> + * AT-mirror failure can revert and keep the two halves consistent.
> + * Pre-clear cc_block_mask so a silent firmware no-op (status
> + * SUCCESS but staging not updated) shows as a zero readback
> + * rather than carrying stale data from a prior op.
> + */
> + if (need_at_mirror) {
> + cbqri_set_cbm(ctrl, 0);
> + err = cbqri_cc_alloc_op(ctrl, CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT,
> + closid, cfg->at);
> + if (err < 0)
> + goto out;
> + saved_cbm = ioread64(ctrl->base + CBQRI_CC_BLOCK_MASK_OFF);
> + }
> +
> + /* Set capacity block mask (cc_block_mask) */
> + cbqri_set_cbm(ctrl, cfg->cbm);
> +
> + /* Capacity config limit operation for the AT half implied by cfg->at */
> + err = cbqri_cc_alloc_op(ctrl, CBQRI_CC_ALLOC_CTL_OP_CONFIG_LIMIT,
> + closid, cfg->at);
> + if (err < 0)
> + goto out;
> +
> + /*
> + * CDP-off mirror: on AT-capable controllers, also program the
> + * other AT half with the same mask so the two halves stay in sync.
> + */
> + if (need_at_mirror) {
> + u32 other = (cfg->at == CBQRI_CONTROL_REGISTERS_AT_CODE) ?
> + CBQRI_CONTROL_REGISTERS_AT_DATA :
> + CBQRI_CONTROL_REGISTERS_AT_CODE;
> +
> + cbqri_set_cbm(ctrl, cfg->cbm);
> + err = cbqri_cc_alloc_op(ctrl,
> + CBQRI_CC_ALLOC_CTL_OP_CONFIG_LIMIT,
> + closid, other);
> + if (err < 0) {
> + int rerr;
> +
> + /*
> + * Best-effort revert of the cfg->at half so the two
> + * halves stay in sync. A schemata read sees only one
> + * half, so silent divergence would otherwise report
> + * the new value as if the write had succeeded.
> + */
> + cbqri_set_cbm(ctrl, saved_cbm);
> + rerr = cbqri_cc_alloc_op(ctrl,
> + CBQRI_CC_ALLOC_CTL_OP_CONFIG_LIMIT,
> + closid, cfg->at);
> + if (rerr < 0)
> + pr_err_ratelimited("AT-mirror revert failed (err=%d), AT halves diverged\n",
> + rerr);
> + goto out;
> + }
> + }
> +
> + /* Clear cc_block_mask before read limit to verify op works */
> + cbqri_set_cbm(ctrl, 0);
> +
> + /* Perform a capacity read limit operation to verify blockmask */
> + err = cbqri_cc_alloc_op(ctrl, CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT,
> + closid, cfg->at);
> + if (err < 0)
> + goto out;
> +
> + /*
> + * Read capacity blockmask and narrow to u32 to match resctrl's CBM
> + * width. cbqri_probe_cc() rejects ncblks > 32 so the upper bits are
> + * reserved zero.
> + */
> + reg = ioread64(ctrl->base + CBQRI_CC_BLOCK_MASK_OFF);
> + if (lower_32_bits(reg) != cfg->cbm) {
> + pr_err_ratelimited("CBM verify mismatch (reg=%llx != cbm=%llx)\n",
> + reg, cfg->cbm);
> + err = -EIO;
> + }
> +
> +out:
> + mutex_unlock(&ctrl->lock);
> + return err;
> +}
> +
> +/*
> + * Read the configured CBM for closid on the at half via READ_LIMIT.
> + * Pre-clears cc_block_mask before the op so a silent firmware no-op
> + * (status SUCCESS but staging not updated) is detectable in cbm_out.
> + */
> +int cbqri_read_cache_config(struct cbqri_controller *ctrl, u32 closid,
> + u32 at, u32 *cbm_out)
> +{
> + int err;
> +
> + mutex_lock(&ctrl->lock);
> + cbqri_set_cbm(ctrl, 0);
> + err = cbqri_cc_alloc_op(ctrl, CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT, closid, at);
> + if (err == 0) {
> + /*
> + * cc_block_mask is a 64-bit MMIO register. resctrl exposes the
> + * CBM as a u32. cbqri_probe_cc() rejects ncblks > 32 so the
> + * upper 32 bits are reserved zero by the spec. Narrow
> + * explicitly via lower_32_bits() so the assumption is visible
> + * at the read site.
> + */
> + *cbm_out = lower_32_bits(ioread64(ctrl->base + CBQRI_CC_BLOCK_MASK_OFF));
> + }
> + mutex_unlock(&ctrl->lock);
> + return err;
> +}
> +
> +static int cbqri_probe_feature(struct cbqri_controller *ctrl, int reg_offset,
> + int operation, int *status, bool *access_type_supported)
> +{
> + const u64 active_mask = CBQRI_CONTROL_REGISTERS_OP_MASK |
> + CBQRI_CONTROL_REGISTERS_AT_MASK |
> + CBQRI_CONTROL_REGISTERS_RCID_MASK;
> + u64 reg, saved_reg;
> + int at;
> +
> + /*
> + * Default the output to false so the status==0 (feature not
> + * implemented) path returns a deterministic value to the caller
> + * rather than leaving an uninitialized bool.
> + */
> + *access_type_supported = false;
> +
> + /* Keep the initial register value to preserve the WPRI fields */
> + reg = ioread64(ctrl->base + reg_offset);
> + saved_reg = reg;
> +
> + /* Drain any in-flight firmware op before issuing our own write. */
> + if (cbqri_wait_busy_flag(ctrl, reg_offset, &saved_reg) < 0) {
> + pr_err("BUSY timeout before probe operation\n");
> + return -EIO;
> + }
> +
> + /*
> + * Execute the requested operation with all active fields
> + * (OP/AT/RCID) zeroed except OP itself. Every bit not in
> + * active_mask is WPRI and gets carried over from saved_reg.
> + */
> + reg = (saved_reg & ~active_mask) |
> + FIELD_PREP(CBQRI_CONTROL_REGISTERS_OP_MASK, operation);
> + iowrite64(reg, ctrl->base + reg_offset);
> + if (cbqri_wait_busy_flag(ctrl, reg_offset, ®) < 0) {
> + pr_err_ratelimited("BUSY timeout during operation\n");
> + return -EIO;
> + }
> +
> + /* Get the operation status */
> + *status = FIELD_GET(CBQRI_CONTROL_REGISTERS_STATUS_MASK, reg);
> +
> + /*
> + * Check for the AT support if the register is implemented
> + * (if not, the status value will remain 0)
> + */
> + if (*status != 0) {
> + /*
> + * Re-issue operation with AT=CODE so the controller
> + * latches AT=CODE on supported hardware (or resets it to 0
> + * on hardware that doesn't). OP must be a defined CBQRI op
> + * here. OP=0 is a no-op and would silently disable CDP.
> + */
> + reg = (saved_reg & ~active_mask) |
> + FIELD_PREP(CBQRI_CONTROL_REGISTERS_OP_MASK, operation) |
> + FIELD_PREP(CBQRI_CONTROL_REGISTERS_AT_MASK,
> + CBQRI_CONTROL_REGISTERS_AT_CODE);
> + iowrite64(reg, ctrl->base + reg_offset);
> + if (cbqri_wait_busy_flag(ctrl, reg_offset, ®) < 0) {
> + pr_err("BUSY timeout setting AT field\n");
> + return -EIO;
> + }
> +
> + /*
> + * If the AT field value has been reset to zero,
> + * then the AT support is not present
> + */
> + at = FIELD_GET(CBQRI_CONTROL_REGISTERS_AT_MASK, reg);
> + if (at == CBQRI_CONTROL_REGISTERS_AT_CODE)
> + *access_type_supported = true;
> + }
> +
> + /*
> + * Restore the original register value.
> + * Clear OP to avoid re-triggering the probe op.
> + */
> + saved_reg &= ~CBQRI_CONTROL_REGISTERS_OP_MASK;
> + iowrite64(saved_reg, ctrl->base + reg_offset);
> + if (cbqri_wait_busy_flag(ctrl, reg_offset, NULL) < 0) {
> + pr_err("BUSY timeout restoring register value\n");
> + return -EIO;
> + }
> +
> + return 0;
> +}
> +
> +static int cbqri_probe_cc(struct cbqri_controller *ctrl)
> +{
> + int err, status;
> + int ver_major, ver_minor;
> + u64 reg;
> +
> + reg = ioread64(ctrl->base + CBQRI_CC_CAPABILITIES_OFF);
> + if (reg == 0)
> + return -ENODEV;
> +
> + ver_minor = FIELD_GET(CBQRI_CC_CAPABILITIES_VER_MINOR_MASK, reg);
> + ver_major = FIELD_GET(CBQRI_CC_CAPABILITIES_VER_MAJOR_MASK, reg);
> + ctrl->cc.ncblks = FIELD_GET(CBQRI_CC_CAPABILITIES_NCBLKS_MASK, reg);
> +
> + pr_debug("version=%d.%d ncblks=%d cache_level=%d\n",
> + ver_major, ver_minor,
> + ctrl->cc.ncblks, ctrl->cache.cache_level);
> +
> + /*
> + * NCBLKS == 0 would divide-by-zero in the schemata math while
> + * ctrl->lock is held.
> + */
> + if (!ctrl->cc.ncblks) {
> + pr_warn("CC at %pa has 0 capacity blocks, skipping\n",
> + &ctrl->addr);
> + return -ENODEV;
> + }
> +
> + if (ctrl->cc.ncblks > 32) {
> + pr_warn("CC at %pa has ncblks=%u > 32 (resctrl CBM is u32), skipping\n",
> + &ctrl->addr, ctrl->cc.ncblks);
> + return -ENODEV;
> + }
> +
> + /* Probe allocation features */
> + err = cbqri_probe_feature(ctrl, CBQRI_CC_ALLOC_CTL_OFF,
> + CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT,
> + &status, &ctrl->cc.supports_alloc_at_code);
> + if (err)
> + return err;
> +
> + if (status == CBQRI_CC_ALLOC_CTL_STATUS_SUCCESS)
> + ctrl->alloc_capable = true;
> +
> + return 0;
> +}
> +
> +static int cbqri_probe_controller(struct cbqri_controller *ctrl)
> +{
> + int err;
> +
> + pr_debug("controller info: type=%d addr=%pa size=%pa max-rcid=%u\n",
> + ctrl->type, &ctrl->addr, &ctrl->size, ctrl->rcid_count);
> +
> + if (!ctrl->addr) {
> + pr_warn("controller has invalid addr=0x0, skipping\n");
> + return -EINVAL;
> + }
> +
> + if (ctrl->size < CBQRI_CTRL_MIN_REG_SPAN) {
> + pr_warn("controller at %pa: size %pa < minimum 0x%x, skipping\n",
> + &ctrl->addr, &ctrl->size, CBQRI_CTRL_MIN_REG_SPAN);
> + return -EINVAL;
> + }
> +
> + if (!request_mem_region(ctrl->addr, ctrl->size, "cbqri_controller")) {
> + pr_err("request_mem_region failed for %pa\n", &ctrl->addr);
> + return -EBUSY;
> + }
> +
> + ctrl->base = ioremap(ctrl->addr, ctrl->size);
> + if (!ctrl->base) {
> + pr_err("ioremap failed for %pa\n", &ctrl->addr);
> + err = -ENOMEM;
> + goto err_release;
> + }
> +
> + switch (ctrl->type) {
> + case CBQRI_CONTROLLER_TYPE_CAPACITY:
> + err = cbqri_probe_cc(ctrl);
> + break;
> + default:
> + pr_err("unknown controller type %d\n", ctrl->type);
> + err = -ENODEV;
> + break;
> + }
> +
> + if (err)
> + goto err_iounmap;
> +
> + return 0;
> +
> +err_iounmap:
> + iounmap(ctrl->base);
> + ctrl->base = NULL;
> +err_release:
> + release_mem_region(ctrl->addr, ctrl->size);
> + return err;
> +}
> +
> +void cbqri_controller_destroy(struct cbqri_controller *ctrl)
> +{
> + /*
> + * cbqri_probe_controller() clears ctrl->base on its error paths and
> + * releases the mem region itself, so reach into both only when
> + * destroy is rolling back a successful probe.
> + */
> + if (ctrl->base) {
> + iounmap(ctrl->base);
> + release_mem_region(ctrl->addr, ctrl->size);
> + }
> + kfree(ctrl);
> +}
> +
> +/**
> + * riscv_cbqri_register_cc_dt() - register a DT-described capacity controller
> + * @info: registration descriptor. info->cache_id is used as the
> + * resctrl domain id. info->type must be CAPACITY.
> + * @cache_level: cache level (2 or 3) the controller backs, mapped to the
> + * resctrl L2/L3 resource by the resctrl glue.
> + * @cpu_mask: CPUs that share this cache.
> + *
> + * The cache topology is supplied directly by the caller. A device-tree
> + * platform driver that already knows which CPUs share the cache and at what
> + * level passes that in. There is no firmware table to resolve it from.
> + *
> + * Return: 0 on success, or a negative errno on failure.
> + */
> +int riscv_cbqri_register_cc_dt(const struct cbqri_controller_info *info,
> + u32 cache_level, const struct cpumask *cpu_mask)
> +{
> + struct cbqri_controller *ctrl;
> + int err;
> +
> + if (!info->addr) {
> + pr_warn("skipping controller with invalid addr=0x0\n");
> + return -EINVAL;
> + }
> +
> + if (info->type != CBQRI_CONTROLLER_TYPE_CAPACITY) {
> + pr_warn("register_cc_dt called with non-capacity type %u\n",
> + info->type);
> + return -EINVAL;
> + }
> +
> + if (!cpu_mask || cpumask_empty(cpu_mask)) {
> + pr_warn("register_cc_dt called with empty cpu_mask\n");
> + return -EINVAL;
> + }
> +
> + ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL);
> + if (!ctrl)
> + return -ENOMEM;
> +
> + mutex_init(&ctrl->lock);
> +
> + ctrl->addr = info->addr;
> + ctrl->size = info->size;
> + ctrl->type = info->type;
> + ctrl->rcid_count = info->rcid_count;
> +
> + /*
> + * SRMCFG encodes RCID in 12 bits. Reject an out-of-range count rather
> + * than silently truncating in every FIELD_PREP(SRMCFG_RCID_MASK, closid)
> + * on the schedule-in fast path.
> + */
> + if (ctrl->rcid_count > FIELD_MAX(SRMCFG_RCID_MASK) + 1) {
> + pr_warn("CC at %pa has RCID count %u beyond the 12-bit SRMCFG field, skipping\n",
> + &ctrl->addr, ctrl->rcid_count);
> + cbqri_controller_destroy(ctrl);
> + return -EINVAL;
> + }
> +
> + ctrl->cache.cache_id = info->cache_id;
> + ctrl->cache.cache_level = cache_level;
> + cpumask_copy(&ctrl->cache.cpu_mask, cpu_mask);
> +
> + err = cbqri_probe_controller(ctrl);
> + if (err) {
> + cbqri_controller_destroy(ctrl);
> + return err;
> + }
> +
> + /*
> + * Allocation capability comes from the capabilities register probed
> + * above, not from device tree. rcid_count only bounds the RCID range,
> + * so a controller the hardware reports as alloc-capable but described
> + * with no RCID count cannot be driven. Reject that inconsistency. A
> + * monitoring-only controller (not alloc_capable) needs no RCID count.
> + */
> + if (ctrl->alloc_capable && !ctrl->rcid_count) {
> + pr_warn("CC at %pa is alloc-capable but has no RCID count, skipping\n",
> + &ctrl->addr);
> + cbqri_controller_destroy(ctrl);
> + return -EINVAL;
> + }
> +
> + mutex_lock(&cbqri_controllers_lock);
> + list_add_tail(&ctrl->list, &cbqri_controllers);
> + mutex_unlock(&cbqri_controllers_lock);
> + return 0;
> +}
> diff --git a/drivers/resctrl/cbqri_internal.h b/drivers/resctrl/cbqri_internal.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..2192a3831bcde7cd4fddf03f2ce1103902e8815c
> --- /dev/null
> +++ b/drivers/resctrl/cbqri_internal.h
> @@ -0,0 +1,107 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +#ifndef _DRIVERS_RESCTRL_CBQRI_INTERNAL_H
> +#define _DRIVERS_RESCTRL_CBQRI_INTERNAL_H
> +
> +#include <linux/bitfield.h>
> +#include <linux/riscv_cbqri.h>
> +#include <linux/cpumask.h>
> +#include <linux/list.h>
> +#include <linux/mutex.h>
> +#include <linux/types.h>
> +
> +/* Capacity Controller (CC) MMIO register offsets. */
> +#define CBQRI_CC_CAPABILITIES_OFF 0
> +#define CBQRI_CC_ALLOC_CTL_OFF 24
> +#define CBQRI_CC_BLOCK_MASK_OFF 32
> +
> +/*
> + * Highest defined register offset (0x20) plus the 8-byte register width.
> + * cbqri_probe_controller() rejects smaller mappings so a u64 access at
> + * BLOCK_MASK stays in bounds.
> + */
> +#define CBQRI_CTRL_MIN_REG_SPAN 0x28u
> +
> +#define CBQRI_CC_CAPABILITIES_VER_MINOR_MASK GENMASK_ULL(3, 0)
> +#define CBQRI_CC_CAPABILITIES_VER_MAJOR_MASK GENMASK_ULL(7, 4)
> +#define CBQRI_CC_CAPABILITIES_NCBLKS_MASK GENMASK_ULL(23, 8)
> +
> +/*
> + * CC control registers are 64-bit. Keep every field mask GENMASK_ULL so
> + * FIELD_MODIFY() or ~mask on a u64 register never zero-extends a 32-bit
> + * mask and clobbers STATUS/BUSY/WPRI in bits 63:32 if RV32 support is
> + * added in the future.
> + */
> +#define CBQRI_CONTROL_REGISTERS_OP_MASK GENMASK_ULL(4, 0)
> +#define CBQRI_CONTROL_REGISTERS_AT_MASK GENMASK_ULL(7, 5)
> +/* AT field values (CBQRI Table 1): data vs code half for CDP */
> +#define CBQRI_CONTROL_REGISTERS_AT_DATA 0
> +#define CBQRI_CONTROL_REGISTERS_AT_CODE 1
> +#define CBQRI_CONTROL_REGISTERS_RCID_MASK GENMASK_ULL(19, 8)
> +#define CBQRI_CONTROL_REGISTERS_STATUS_MASK GENMASK_ULL(38, 32)
> +#define CBQRI_CONTROL_REGISTERS_BUSY_MASK GENMASK_ULL(39, 39)
> +
> +#define CBQRI_CC_ALLOC_CTL_OP_CONFIG_LIMIT 1
> +#define CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT 2
> +#define CBQRI_CC_ALLOC_CTL_STATUS_SUCCESS 1
> +
> +/* Capacity Controller hardware capabilities */
> +struct riscv_cbqri_capacity_caps {
> + u16 ncblks;
> + bool supports_alloc_at_code;
> +};
> +
> +/**
> + * struct cbqri_cc_config - desired capacity allocation state for one rcid
> + * @cbm: capacity block mask
> + * @at: AT half the @cbm applies to (CBQRI_CONTROL_REGISTERS_AT_DATA
> + * or CBQRI_CONTROL_REGISTERS_AT_CODE)
> + * @cdp_enabled: when false and the controller supports AT, mirror @cbm
> + * into the other AT half so both stay in sync
> + */
> +struct cbqri_cc_config {
> + u64 cbm;
> + u32 at;
> + bool cdp_enabled;
> +};
> +
> +struct cbqri_controller {
> + void __iomem *base;
> + /*
> + * Serializes the write-then-poll-busy MMIO sequences on this
> + * controller. Each CBQRI op may busy-wait up to 1 ms on slow
> + * firmware, so use a sleeping mutex to keep preemption enabled.
> + * All resctrl-arch entry points run in process context.
> + */
> + struct mutex lock;
> +
> + struct riscv_cbqri_capacity_caps cc;
> +
> + bool alloc_capable;
> +
> + phys_addr_t addr;
> + phys_addr_t size;
> + enum cbqri_controller_type type;
> + u32 rcid_count;
> +
> + struct list_head list;
> +
> + struct cache_controller {
> + u32 cache_level;
> + struct cpumask cpu_mask;
> + /* Cache id used as the resctrl domain id */
> + u32 cache_id;
> + } cache;
> +};
> +
> +extern struct list_head cbqri_controllers;
> +extern struct mutex cbqri_controllers_lock;
> +
> +void cbqri_controller_destroy(struct cbqri_controller *ctrl);
> +
> +int cbqri_apply_cache_config(struct cbqri_controller *ctrl, u32 closid,
> + const struct cbqri_cc_config *cfg);
> +
> +int cbqri_read_cache_config(struct cbqri_controller *ctrl, u32 closid,
> + u32 at, u32 *cbm_out);
> +
> +#endif /* _DRIVERS_RESCTRL_CBQRI_INTERNAL_H */
> diff --git a/include/linux/riscv_cbqri.h b/include/linux/riscv_cbqri.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..58737224d2f2c67a443f78f9fea7f368c7d16044
> --- /dev/null
> +++ b/include/linux/riscv_cbqri.h
> @@ -0,0 +1,47 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Public registration API for the RISC-V Capacity and Bandwidth QoS
> + * Register Interface (CBQRI) core. Discovery layers (device tree
> + * platform drivers) call riscv_cbqri_register_cc_dt() to hand a capacity
> + * controller descriptor to the core, which owns all subsequent state.
> + */
> +#ifndef _LINUX_RISCV_CBQRI_H
> +#define _LINUX_RISCV_CBQRI_H
> +
> +#include <linux/types.h>
> +
> +struct cpumask;
> +
> +enum cbqri_controller_type {
> + CBQRI_CONTROLLER_TYPE_CAPACITY,
> +};
> +
> +/**
> + * struct cbqri_controller_info - registration descriptor
> + * @addr: MMIO base address of the controller's register interface
> + * @size: size of the MMIO region
> + * @type: controller type (capacity)
> + * @rcid_count: number of supported RCIDs
> + * @cache_id: cache id used as the resctrl domain id
> + */
> +struct cbqri_controller_info {
> + phys_addr_t addr;
> + phys_addr_t size;
> + enum cbqri_controller_type type;
> + u32 rcid_count;
> + u32 cache_id;
> +};
> +
> +#if IS_ENABLED(CONFIG_RISCV_CBQRI)
> +int riscv_cbqri_register_cc_dt(const struct cbqri_controller_info *info,
> + u32 cache_level, const struct cpumask *cpu_mask);
> +#else
> +static inline int
> +riscv_cbqri_register_cc_dt(const struct cbqri_controller_info *info,
> + u32 cache_level, const struct cpumask *cpu_mask)
> +{
> + return -ENODEV;
> +}
> +#endif
> +
> +#endif /* _LINUX_RISCV_CBQRI_H */
>
> --
> 2.34.1
>
Thanks,
Yunhui
^ permalink raw reply
* Re: [External] [PATCH v2 3/8] riscv: Add support for srmcfg CSR from Ssqosid extension
From: yunhui cui @ 2026-06-27 9:38 UTC (permalink / raw)
To: Drew Fustini
Cc: Adrien Ricciardi, Alexandre Ghiti, Atish Kumar Patra, Atish Patra,
Babu Moger, Ben Horgan, Borislav Petkov, Chen Pei, Conor Dooley,
Conor Dooley, Dave Hansen, Dave Martin, Fenghua Yu, Gong Shuai,
Gong Shuai, guo.wenjia23, James Morse, Kornel Dulęba,
Krzysztof Kozlowski, liu.qingtao2, Liu Zhiwei, Palmer Dabbelt,
Paul Walmsley, Peter Newman, Radim Krčmář,
Reinette Chatre, Rob Herring, Samuel Holland,
Sebastian Andrzej Siewior, Tony Luck, Vasudevan Srinivasan,
Ved Shanbhogue, Weiwei Li, linux-kernel, linux-riscv, x86,
devicetree, linux-rt-devel, linux-doc
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-3-2f8049fd902b@kernel.org>
Hi Drew,
On Thu, Jun 25, 2026 at 9:40 AM Drew Fustini <fustini@kernel.org> wrote:
>
> Add support for the srmcfg CSR defined in the Ssqosid ISA extension.
> The CSR contains two fields:
>
> - Resource Control ID (RCID) for resource allocation
> - Monitoring Counter ID (MCID) for tracking resource usage
>
> Requests from a hart to shared resources are tagged with these IDs,
> allowing resource usage to be associated with the running task.
>
> Add a srmcfg field to thread_struct with the same format as the CSR so
> the scheduler can set the RCID and MCID for each task on context
> switch. A per-cpu cpu_srmcfg variable mirrors the CSR state to avoid
> redundant writes. L1D-hot memory access is faster than a CSR read and
> avoids traps under virtualization.
>
> A per-cpu cpu_srmcfg_default holds the default srmcfg for each CPU as
> set by resctrl CPU group assignment. On context switch, RCID and MCID
> inherit from the CPU default independently: a task whose thread RCID
> field is zero takes the CPU default's RCID, and likewise for MCID.
>
> Link: https://github.com/riscv/riscv-ssqosid/releases/tag/v1.0
> Assisted-by: Claude:claude-opus-4-7
> Co-developed-by: Kornel Dulęba <mindal@semihalf.com>
> Signed-off-by: Kornel Dulęba <mindal@semihalf.com>
> Signed-off-by: Drew Fustini <fustini@kernel.org>
> ---
> MAINTAINERS | 8 ++++
> arch/riscv/Kconfig | 18 +++++++
> arch/riscv/include/asm/csr.h | 5 ++
> arch/riscv/include/asm/processor.h | 3 ++
> arch/riscv/include/asm/qos.h | 83 ++++++++++++++++++++++++++++++++
> arch/riscv/include/asm/switch_to.h | 3 ++
> arch/riscv/kernel/Makefile | 2 +
> arch/riscv/kernel/qos.c | 98 ++++++++++++++++++++++++++++++++++++++
> 8 files changed, 220 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 0b9d7c8276acbafdb28a0ea5e81aa853ebee50b9..07109e1a8f8470377916c98074ab68fec51dfdc6 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -23293,6 +23293,14 @@ F: drivers/perf/riscv_pmu.c
> F: drivers/perf/riscv_pmu_legacy.c
> F: drivers/perf/riscv_pmu_sbi.c
>
> +RISC-V QOS RESCTRL SUPPORT
> +M: Drew Fustini <fustini@kernel.org>
> +R: yunhui cui <cuiyunhui@bytedance.com>
> +L: linux-riscv@lists.infradead.org
> +S: Supported
> +F: arch/riscv/include/asm/qos.h
> +F: arch/riscv/kernel/qos.c
> +
> RISC-V RPMI AND MPXY DRIVERS
> M: Rahul Pathak <rahul@summations.net>
> M: Anup Patel <anup@brainfault.org>
> diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
> index 3f0a647218e407f72890e83722ba8472858c1a59..ee586925f97227668c228b5481c05a2f914d928c 100644
> --- a/arch/riscv/Kconfig
> +++ b/arch/riscv/Kconfig
> @@ -590,6 +590,24 @@ config RISCV_ISA_SVNAPOT
>
> If you don't know what to do here, say Y.
>
> +config RISCV_ISA_SSQOSID
> + bool "Ssqosid extension support for supervisor mode Quality of Service ID"
> + depends on 64BIT
Why is 64BIT required here?
> + default n
> + help
> + Adds support for the Ssqosid ISA extension (Supervisor-mode
> + Quality of Service ID).
> +
> + Ssqosid defines the srmcfg CSR which allows the system to tag the
> + running process with an RCID (Resource Control ID) and MCID
> + (Monitoring Counter ID). The RCID is used to determine resource
> + allocation. The MCID is used to track resource usage in event
> + counters.
> +
> + For example, a cache controller may use the RCID to apply a
> + cache partitioning scheme and use the MCID to track how much
> + cache a process, or a group of processes, is using.
> +
> config RISCV_ISA_SVPBMT
> bool "Svpbmt extension support for supervisor mode page-based memory types"
> depends on 64BIT && MMU
> diff --git a/arch/riscv/include/asm/csr.h b/arch/riscv/include/asm/csr.h
> index 31b8988f4488daa89b854ccc97c4efe1c82bcc3e..7bce928e5daa09bd62f0917279b04cfad30f46f5 100644
> --- a/arch/riscv/include/asm/csr.h
> +++ b/arch/riscv/include/asm/csr.h
> @@ -84,6 +84,10 @@
> #define SATP_ASID_MASK _AC(0xFFFF, UL)
> #endif
>
> +/* SRMCFG fields */
> +#define SRMCFG_RCID_MASK GENMASK(11, 0)
> +#define SRMCFG_MCID_MASK GENMASK(27, 16)
> +
> /* Exception cause high bit - is an interrupt if set */
> #define CAUSE_IRQ_FLAG (_AC(1, UL) << (__riscv_xlen - 1))
>
> @@ -328,6 +332,7 @@
> #define CSR_STVAL 0x143
> #define CSR_SIP 0x144
> #define CSR_SATP 0x180
> +#define CSR_SRMCFG 0x181
>
> #define CSR_STIMECMP 0x14D
> #define CSR_STIMECMPH 0x15D
> diff --git a/arch/riscv/include/asm/processor.h b/arch/riscv/include/asm/processor.h
> index 812517b2cec1350f741849c1c56a35027321ef50..49a386d74cd3f0603a3ff919059d077a7e4d513c 100644
> --- a/arch/riscv/include/asm/processor.h
> +++ b/arch/riscv/include/asm/processor.h
> @@ -123,6 +123,9 @@ struct thread_struct {
> /* A forced icache flush is not needed if migrating to the previous cpu. */
> unsigned int prev_cpu;
> #endif
> +#ifdef CONFIG_RISCV_ISA_SSQOSID
> + u32 srmcfg;
> +#endif
> };
>
> /* Whitelist the fstate from the task_struct for hardened usercopy */
> diff --git a/arch/riscv/include/asm/qos.h b/arch/riscv/include/asm/qos.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..e9e1d69f3797be5f89785a9b3aa7d9d51c476a8a
> --- /dev/null
> +++ b/arch/riscv/include/asm/qos.h
> @@ -0,0 +1,83 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef _ASM_RISCV_QOS_H
> +#define _ASM_RISCV_QOS_H
> +
> +#include <linux/percpu-defs.h>
> +
> +#ifdef CONFIG_RISCV_ISA_SSQOSID
> +
> +#include <linux/bitfield.h>
> +#include <linux/cpufeature.h>
> +#include <linux/sched.h>
> +
> +#include <asm/csr.h>
> +#include <asm/fence.h>
> +#include <asm/hwcap.h>
> +
> +/* cached value of srmcfg csr for each cpu */
> +DECLARE_PER_CPU(u32, cpu_srmcfg);
> +
> +/* default srmcfg value for each cpu, set via resctrl cpu assignment */
> +DECLARE_PER_CPU(u32, cpu_srmcfg_default);
> +
> +static inline void __switch_to_srmcfg(struct task_struct *next)
> +{
> + u32 thread_srmcfg, default_srmcfg;
> +
> + thread_srmcfg = READ_ONCE(next->thread.srmcfg);
> + default_srmcfg = __this_cpu_read(cpu_srmcfg_default);
> +
> + /*
> + * RCID and MCID inherit from cpu_srmcfg_default independently.
> + * RESCTRL_RESERVED_CLOSID and RESCTRL_RESERVED_RMID are both 0, so a
> + * zero field means "unassigned" and takes the CPU default.
> + */
> + if (thread_srmcfg == 0) {
> + thread_srmcfg = default_srmcfg;
> + } else {
> + u32 rcid = FIELD_GET(SRMCFG_RCID_MASK, thread_srmcfg);
> + u32 mcid = FIELD_GET(SRMCFG_MCID_MASK, thread_srmcfg);
> +
> + if (rcid == 0 || mcid == 0) {
> + if (rcid == 0)
> + rcid = FIELD_GET(SRMCFG_RCID_MASK, default_srmcfg);
> + if (mcid == 0)
> + mcid = FIELD_GET(SRMCFG_MCID_MASK, default_srmcfg);
> + thread_srmcfg = FIELD_PREP(SRMCFG_RCID_MASK, rcid) |
> + FIELD_PREP(SRMCFG_MCID_MASK, mcid);
> + }
> + }
> +
> + if (thread_srmcfg != __this_cpu_read(cpu_srmcfg)) {
> + /*
> + * Drain stores from the outgoing task before the CSR write
> + * so they retain the previous RCID/MCID tag at the cache
> + * interconnect.
> + */
> + RISCV_FENCE(rw, o);
> +
> + __this_cpu_write(cpu_srmcfg, thread_srmcfg);
> + csr_write(CSR_SRMCFG, thread_srmcfg);
> + /*
> + * Order the csrw before the new task's loads/stores so they
> + * pick up the new tag. Zicsr 6.1.1 makes CSR writes weakly
> + * ordered (device-output) vs memory ops. Ssqosid v1.0 is
> + * silent so honor the general CSR rule.
> + */
> + RISCV_FENCE(o, rw);
> + }
> +}
> +
> +static __always_inline bool has_srmcfg(void)
> +{
> + return riscv_has_extension_unlikely(RISCV_ISA_EXT_SSQOSID);
> +}
> +
> +#else /* ! CONFIG_RISCV_ISA_SSQOSID */
> +
> +struct task_struct;
> +static __always_inline bool has_srmcfg(void) { return false; }
> +static inline void __switch_to_srmcfg(struct task_struct *next) { }
> +
> +#endif /* CONFIG_RISCV_ISA_SSQOSID */
> +#endif /* _ASM_RISCV_QOS_H */
> diff --git a/arch/riscv/include/asm/switch_to.h b/arch/riscv/include/asm/switch_to.h
> index 0e71eb82f920cac2f14bb626879bb219a2f247cc..1c7ea53ec012adeaf03bf7c5d549ab21849768b5 100644
> --- a/arch/riscv/include/asm/switch_to.h
> +++ b/arch/riscv/include/asm/switch_to.h
> @@ -14,6 +14,7 @@
> #include <asm/processor.h>
> #include <asm/ptrace.h>
> #include <asm/csr.h>
> +#include <asm/qos.h>
>
> #ifdef CONFIG_FPU
> extern void __fstate_save(struct task_struct *save_to);
> @@ -119,6 +120,8 @@ do { \
> __switch_to_fpu(__prev, __next); \
> if (has_vector() || has_xtheadvector()) \
> __switch_to_vector(__prev, __next); \
> + if (has_srmcfg()) \
> + __switch_to_srmcfg(__next); \
> if (switch_to_should_flush_icache(__next)) \
> local_flush_icache_all(); \
> __switch_to_envcfg(__next); \
> diff --git a/arch/riscv/kernel/Makefile b/arch/riscv/kernel/Makefile
> index cabb99cadfb6d1e1284d6b4e9ae76044d36949f5..ebe1c3588177b4b825a52af9ca17e17b5561427c 100644
> --- a/arch/riscv/kernel/Makefile
> +++ b/arch/riscv/kernel/Makefile
> @@ -128,3 +128,5 @@ obj-$(CONFIG_ACPI_NUMA) += acpi_numa.o
>
> obj-$(CONFIG_GENERIC_CPU_VULNERABILITIES) += bugs.o
> obj-$(CONFIG_RISCV_USER_CFI) += usercfi.o
> +
> +obj-$(CONFIG_RISCV_ISA_SSQOSID) += qos.o
> diff --git a/arch/riscv/kernel/qos.c b/arch/riscv/kernel/qos.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..ea33201a43f61534bf28b9c02b62801f30f62154
> --- /dev/null
> +++ b/arch/riscv/kernel/qos.c
> @@ -0,0 +1,98 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +#include <linux/cpu.h>
> +#include <linux/cpu_pm.h>
> +#include <linux/cpuhotplug.h>
> +#include <linux/notifier.h>
> +#include <linux/percpu-defs.h>
> +#include <linux/types.h>
> +
> +#include <asm/cpufeature-macros.h>
> +#include <asm/hwcap.h>
> +#include <asm/qos.h>
> +
> +/*
> + * Cached value of srmcfg csr for each cpu. Seeded to U32_MAX so the next
> + * __switch_to_srmcfg() unconditionally writes the CSR. The encoding
> + * MCID << 16 | RCID with both fields well under 16 bits can never
> + * produce this sentinel. This covers early-boot context switches that
> + * happen before riscv_srmcfg_init() runs as an arch_initcall.
> + */
> +DEFINE_PER_CPU(u32, cpu_srmcfg) = U32_MAX;
> +
> +/* default srmcfg value for each cpu, set via resctrl cpu assignment */
> +DEFINE_PER_CPU(u32, cpu_srmcfg_default);
> +
> +/*
> + * Invalidate the per-CPU srmcfg cache, used as both the cpuhp startup and
> + * teardown callback. The sentinel is a value no real srmcfg encoding can
> + * produce (MCID << 16 | RCID, both fields well under 16 bits) so the next
> + * __switch_to_srmcfg() unconditionally writes the CSR.
> + *
> + * Ssqosid v1.0 leaves CSR state across hart stop/start implementation-
> + * defined, so the cached value cannot be trusted after online. Invalidating
> + * on offline as well means the sentinel persists across the offline period:
> + * a CPU brought back online finds the cache already invalidated before it is
> + * schedulable, closing the window where a task scheduled before the startup
> + * callback runs could match a stale cache and skip the CSR write while the
> + * hardware CSR was reset across hart stop/start.
> + */
> +static int riscv_srmcfg_reset_cache(unsigned int cpu)
> +{
> + per_cpu(cpu_srmcfg, cpu) = U32_MAX;
> + return 0;
> +}
> +
> +/*
> + * CPU PM notifier: invalidate the cached srmcfg on resume from a deep
> + * idle / suspend. Ssqosid v1.0 leaves CSR_SRMCFG state across low-power
> + * transitions implementation-defined, and the boot CPU never goes
> + * through the cpuhp online callback during system suspend, so without
> + * this hook __switch_to_srmcfg() would skip the CSR write when the
> + * outgoing task happens to share its srmcfg with the pre-suspend cache.
> + */
> +static int riscv_srmcfg_pm_notify(struct notifier_block *nb,
> + unsigned long action, void *unused)
> +{
> + switch (action) {
> + case CPU_PM_EXIT:
> + case CPU_PM_ENTER_FAILED:
> + /*
> + * The CSR is implementation-defined across the low-power
> + * transition. Invalidate the cache and eagerly rewrite the
> + * CSR for the current task so it does not run mis-tagged
> + * until the next context switch.
> + */
> + __this_cpu_write(cpu_srmcfg, U32_MAX);
> + __switch_to_srmcfg(current);
> + break;
> + }
> + return NOTIFY_OK;
> +}
> +
> +static struct notifier_block riscv_srmcfg_pm_nb = {
> + .notifier_call = riscv_srmcfg_pm_notify,
> +};
> +
> +static int __init riscv_srmcfg_init(void)
> +{
> + int err;
> +
> + if (!riscv_has_extension_unlikely(RISCV_ISA_EXT_SSQOSID))
> + return 0;
> +
> + /*
> + * cpuhp_setup_state() invokes the startup callback locally on every
> + * already-online CPU, so no separate seed loop is needed here.
> + */
> + err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "riscv/srmcfg:online",
> + riscv_srmcfg_reset_cache, riscv_srmcfg_reset_cache);
> + if (err < 0) {
> + pr_warn("srmcfg cpuhp registration failed (%d), cpus brought online after boot will not invalidate the CSR_SRMCFG cache\n",
> + err);
> + return err;
> + }
> +
> + cpu_pm_register_notifier(&riscv_srmcfg_pm_nb);
> + return 0;
> +}
> +arch_initcall(riscv_srmcfg_init);
>
> --
> 2.34.1
>
Thanks,
Yunhui
^ permalink raw reply
* [PATCH 0/2] arm64: dts: rockchip: Add Vicharak Vaaman2 board support
From: Hrushiraj Gandhi @ 2026-06-27 9:38 UTC (permalink / raw)
To: linux-rockchip, devicetree, linux-arm-kernel
Cc: heiko, robh+dt, krzysztof.kozlowski+dt, conor+dt,
Hrushiraj Gandhi
This patch series adds device tree support for the Vicharak Vaaman2,
a single-board computer based on the Rockchip RK3588 SoC.
The Vaaman2 board features include:
- RK3588 SoC
- eMMC storage and microSD support
- RK806 PMIC and multiple RK8602/RK8603 regulators
- Real-time clock and status LED
- UART serial console and SARADC
Note: This series was generated on top of the pending Vicharak Axon
board support patches (v7) and includes the Axon entry in the context
of the YAML binding patch.
Patch 1 adds the binding documentation for the Vaaman2 board.
Patch 2 adds the actual device tree file and Makefile entry.
Hrushiraj Gandhi (2):
dt-bindings: arm: rockchip: Add Vicharak Vaaman2
arm64: dts: rockchip: Add Vicharak Vaaman2 board
.../devicetree/bindings/arm/rockchip.yaml | 5 +
arch/arm64/boot/dts/rockchip/Makefile | 1 +
.../dts/rockchip/rk3588-vicharak-vaaman2.dts | 547 ++++++++++++++++++
3 files changed, 553 insertions(+)
create mode 100644 arch/arm64/boot/dts/rockchip/rk3588-vicharak-vaaman2.dts
--
2.47.3
^ permalink raw reply
* [PATCH 1/2] dt-bindings: arm: rockchip: Add Vicharak Vaaman2
From: Hrushiraj Gandhi @ 2026-06-27 9:38 UTC (permalink / raw)
To: linux-rockchip, devicetree, linux-arm-kernel
Cc: heiko, robh+dt, krzysztof.kozlowski+dt, conor+dt,
Hrushiraj Gandhi
In-Reply-To: <20260627093831.80495-1-hrushirajg23@gmail.com>
Add device tree binding documentation for the Vicharak Vaaman2,
a single-board computer based on the Rockchip RK3588 SoC.
Signed-off-by: Hrushiraj Gandhi <hrushirajg23@gmail.com>
---
Documentation/devicetree/bindings/arm/rockchip.yaml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Documentation/devicetree/bindings/arm/rockchip.yaml b/Documentation/devicetree/bindings/arm/rockchip.yaml
index b023d4cc9842..b5a43fd5e546 100644
--- a/Documentation/devicetree/bindings/arm/rockchip.yaml
+++ b/Documentation/devicetree/bindings/arm/rockchip.yaml
@@ -1311,6 +1311,11 @@ properties:
- const: vicharak,axon
- const: rockchip,rk3588
+ - description: Vicharak Vaaman2
+ items:
+ - const: vicharak,vaaman2
+ - const: rockchip,rk3588
+
- description: WolfVision PF5 mainboard
items:
- const: wolfvision,rk3568-pf5
--
2.47.3
^ permalink raw reply related
* [PATCH 2/2] arm64: dts: rockchip: Add Vicharak Vaaman2 board
From: Hrushiraj Gandhi @ 2026-06-27 9:38 UTC (permalink / raw)
To: linux-rockchip, devicetree, linux-arm-kernel
Cc: heiko, robh+dt, krzysztof.kozlowski+dt, conor+dt,
Hrushiraj Gandhi
In-Reply-To: <20260627093831.80495-1-hrushirajg23@gmail.com>
Add device tree for the Vicharak Vaaman2, a single-board computer
based on the Rockchip RK3588 SoC.
The board features:
- RK3588 SoC with 4x Cortex-A76 (big) + 4x Cortex-A55 (little)
- eMMC storage via SDHCI (HS400)
- microSD card via SDMMC
- RK806 PMIC on SPI2 providing all required power domains
- Two RK8602/RK8603 CPU regulators on I2C0 (big clusters)
- RK8602 NPU regulator on I2C1
- HYM8563 RTC on I2C0
- Status LED on GPIO2_C5 (active-low, heartbeat trigger)
- UART2 as serial console at 1500000 baud
- SARADC with 1.8V reference
Signed-off-by: Hrushiraj Gandhi <hrushirajg23@gmail.com>
---
arch/arm64/boot/dts/rockchip/Makefile | 1 +
.../dts/rockchip/rk3588-vicharak-vaaman2.dts | 547 ++++++++++++++++++
2 files changed, 548 insertions(+)
create mode 100644 arch/arm64/boot/dts/rockchip/rk3588-vicharak-vaaman2.dts
diff --git a/arch/arm64/boot/dts/rockchip/Makefile b/arch/arm64/boot/dts/rockchip/Makefile
index 92114cb757cc..9213280d831b 100644
--- a/arch/arm64/boot/dts/rockchip/Makefile
+++ b/arch/arm64/boot/dts/rockchip/Makefile
@@ -214,6 +214,7 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-tiger-haikou-video-demo.dtbo
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-toybrick-x0.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-turing-rk1.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-vicharak-axon.dtb
+dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-vicharak-vaaman2.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-coolpi-4b.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-evb1-v10.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-gameforce-ace.dtb
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-vicharak-vaaman2.dts b/arch/arm64/boot/dts/rockchip/rk3588-vicharak-vaaman2.dts
new file mode 100644
index 000000000000..9ba9688c1213
--- /dev/null
+++ b/arch/arm64/boot/dts/rockchip/rk3588-vicharak-vaaman2.dts
@@ -0,0 +1,547 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+
+/dts-v1/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/linux-event-codes.h>
+#include <dt-bindings/leds/common.h>
+#include <dt-bindings/pinctrl/rockchip.h>
+#include <dt-bindings/soc/rockchip,vop2.h>
+#include "rk3588.dtsi"
+
+/ {
+ model = "Vicharak Vaaman2";
+ compatible = "vicharak,vaaman2", "rockchip,rk3588";
+
+ aliases {
+ mmc0 = &sdmmc;
+ mmc1 = &sdhci;
+ serial2 = &uart2;
+ };
+
+ chosen {
+ stdout-path = "serial2:1500000n8";
+ };
+
+ leds {
+ compatible = "gpio-leds";
+
+ status_led: status-led {
+ gpios = <&gpio2 RK_PC5 GPIO_ACTIVE_LOW>;
+ function = LED_FUNCTION_STATUS;
+ default-state = "on";
+ retain-state-suspended;
+ linux,default-trigger = "heartbeat";
+ };
+
+ };
+
+ vcc20v_dcin: regulator-vcc20v-dcin {
+ compatible = "regulator-fixed";
+ regulator-name = "vcc20v_dcin";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <20000000>;
+ regulator-max-microvolt = <20000000>;
+ };
+
+ vcc5v0_sys: regulator-vcc5v0-sys {
+ compatible = "regulator-fixed";
+ regulator-name = "vcc5v0_sys";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <5000000>;
+ regulator-max-microvolt = <5000000>;
+ vin-supply = <&vcc20v_dcin>;
+ };
+
+ vcc_1v1_nldo_s3: regulator-vcc-1v1-nldo-s3 {
+ compatible = "regulator-fixed";
+ regulator-name = "vcc_1v1_nldo_s3";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1100000>;
+ regulator-max-microvolt = <1100000>;
+ vin-supply = <&vcc5v0_sys>;
+ };
+
+};
+
+&cpu_b0 {
+ cpu-supply = <&vdd_cpu_big0_s0>;
+};
+
+&cpu_b1 {
+ cpu-supply = <&vdd_cpu_big0_s0>;
+};
+
+&cpu_b2 {
+ cpu-supply = <&vdd_cpu_big1_s0>;
+};
+
+&cpu_b3 {
+ cpu-supply = <&vdd_cpu_big1_s0>;
+};
+
+&cpu_l0 {
+ cpu-supply = <&vdd_cpu_lit_s0>;
+};
+
+&cpu_l1 {
+ cpu-supply = <&vdd_cpu_lit_s0>;
+};
+
+&cpu_l2 {
+ cpu-supply = <&vdd_cpu_lit_s0>;
+};
+
+&cpu_l3 {
+ cpu-supply = <&vdd_cpu_lit_s0>;
+};
+
+
+&i2c0 {
+ pinctrl-0 = <&i2c0m2_xfer>;
+ pinctrl-names = "default";
+ status = "okay";
+
+ vdd_cpu_big0_s0: regulator@42 {
+ compatible = "rockchip,rk8602";
+ fcs,suspend-voltage-selector = <1>;
+ reg = <0x42>;
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-max-microvolt = <1050000>;
+ regulator-min-microvolt = <550000>;
+ regulator-name = "vdd_cpu_big0_s0";
+ regulator-ramp-delay = <2300>;
+ vin-supply = <&vcc5v0_sys>;
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_cpu_big1_s0: regulator@43 {
+ compatible = "rockchip,rk8603", "rockchip,rk8602";
+ fcs,suspend-voltage-selector = <1>;
+ reg = <0x43>;
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-max-microvolt = <1050000>;
+ regulator-min-microvolt = <550000>;
+ regulator-name = "vdd_cpu_big1_s0";
+ regulator-ramp-delay = <2300>;
+ vin-supply = <&vcc5v0_sys>;
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ hym8563: rtc@51 {
+ compatible = "haoyu,hym8563";
+ reg = <0x51>;
+ #clock-cells = <0>;
+ clock-output-names = "hym8563";
+ interrupt-parent = <&gpio0>;
+ interrupts = <RK_PB0 IRQ_TYPE_LEVEL_LOW>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&rtc_int>;
+ wakeup-source;
+ };
+};
+
+&i2c1 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&i2c1m2_xfer>;
+ status = "okay";
+
+ vdd_npu_s0: regulator@42 {
+ compatible = "rockchip,rk8602";
+ reg = <0x42>;
+ fcs,suspend-voltage-selector = <1>;
+ regulator-name = "vdd_npu_s0";
+ regulator-boot-on;
+ regulator-enable-ramp-delay = <500>;
+ regulator-min-microvolt = <550000>;
+ regulator-max-microvolt = <950000>;
+ regulator-ramp-delay = <2300>;
+ vin-supply = <&vcc5v0_sys>;
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+};
+
+&sdhci {
+ bus-width = <8>;
+ full-pwr-cycle-in-suspend;
+ mmc-hs400-1_8v;
+ mmc-hs400-enhanced-strobe;
+ no-sd;
+ no-sdio;
+ non-removable;
+ vmmc-supply = <&vcc_3v3_s3>;
+ vqmmc-supply = <&vcc_1v8_s3>;
+ status = "okay";
+};
+
+&sdmmc {
+ bus-width = <4>;
+ cap-mmc-highspeed;
+ cap-sd-highspeed;
+ cd-gpios = <&gpio0 RK_PA4 GPIO_ACTIVE_LOW>;
+ disable-wp;
+ max-frequency = <200000000>;
+ no-mmc;
+ no-sdio;
+ sd-uhs-sdr104;
+ vmmc-supply = <&vcc_3v3_s3>;
+ vqmmc-supply = <&vccio_sd_s0>;
+ status = "okay";
+};
+
+&spi2 {
+ assigned-clock-rates = <200000000>;
+ assigned-clocks = <&cru CLK_SPI2>;
+ num-cs = <1>;
+ pinctrl-0 = <&spi2m2_cs0 &spi2m2_pins>;
+ pinctrl-names = "default";
+ status = "okay";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pmic@0 {
+ reg = <0>;
+ compatible = "rockchip,rk806";
+ #gpio-cells = <2>;
+ gpio-controller;
+ interrupt-parent = <&gpio0>;
+ interrupts = <7 IRQ_TYPE_LEVEL_LOW>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pmic_pins>, <&rk806_dvs1_null>,
+ <&rk806_dvs2_null>, <&rk806_dvs3_null>;
+ spi-max-frequency = <1000000>;
+
+ vcc1-supply = <&vcc5v0_sys>;
+ vcc2-supply = <&vcc5v0_sys>;
+ vcc3-supply = <&vcc5v0_sys>;
+ vcc4-supply = <&vcc5v0_sys>;
+ vcc5-supply = <&vcc5v0_sys>;
+ vcc6-supply = <&vcc5v0_sys>;
+ vcc7-supply = <&vcc5v0_sys>;
+ vcc8-supply = <&vcc5v0_sys>;
+ vcc9-supply = <&vcc5v0_sys>;
+ vcc10-supply = <&vcc5v0_sys>;
+ vcc11-supply = <&vcc_2v0_pldo_s3>;
+ vcc12-supply = <&vcc5v0_sys>;
+ vcc13-supply = <&vcc_1v1_nldo_s3>;
+ vcc14-supply = <&vcc_1v1_nldo_s3>;
+ vcca-supply = <&vcc5v0_sys>;
+
+ rk806_dvs1_null: dvs1-null-pins {
+ pins = "gpio_pwrctrl1";
+ function = "pin_fun0";
+ };
+
+ rk806_dvs2_null: dvs2-null-pins {
+ pins = "gpio_pwrctrl2";
+ function = "pin_fun0";
+ };
+
+ rk806_dvs3_null: dvs3-null-pins {
+ pins = "gpio_pwrctrl3";
+ function = "pin_fun0";
+ };
+
+ regulators {
+ vdd_gpu_s0: dcdc-reg1 {
+ regulator-boot-on;
+ regulator-min-microvolt = <550000>;
+ regulator-max-microvolt = <950000>;
+ regulator-ramp-delay = <12500>;
+ regulator-name = "vdd_gpu_s0";
+ regulator-enable-ramp-delay = <400>;
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_cpu_lit_s0: dcdc-reg2 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <550000>;
+ regulator-max-microvolt = <950000>;
+ regulator-ramp-delay = <12500>;
+ regulator-name = "vdd_cpu_lit_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_log_s0: dcdc-reg3 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <675000>;
+ regulator-max-microvolt = <750000>;
+ regulator-ramp-delay = <12500>;
+ regulator-name = "vdd_log_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ regulator-suspend-microvolt = <750000>;
+ };
+ };
+
+ vdd_vdenc_s0: dcdc-reg4 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <550000>;
+ regulator-max-microvolt = <950000>;
+ regulator-ramp-delay = <12500>;
+ regulator-name = "vdd_vdenc_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_ddr_s0: dcdc-reg5 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <675000>;
+ regulator-max-microvolt = <900000>;
+ regulator-ramp-delay = <12500>;
+ regulator-name = "vdd_ddr_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ regulator-suspend-microvolt = <850000>;
+ };
+ };
+
+ vdd2_ddr_s3: dcdc-reg6 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-name = "vdd2_ddr_s3";
+
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ };
+ };
+
+ vcc_2v0_pldo_s3: dcdc-reg7 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <2000000>;
+ regulator-max-microvolt = <2000000>;
+ regulator-name = "vdd_2v0_pldo_s3";
+
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ regulator-suspend-microvolt = <2000000>;
+ };
+ };
+
+ vcc_3v3_s3: dcdc-reg8 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-name = "vcc_3v3_s3";
+
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ regulator-suspend-microvolt = <3300000>;
+ };
+ };
+
+ vddq_ddr_s0: dcdc-reg9 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-name = "vddq_ddr_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vcc_1v8_s3: dcdc-reg10 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-name = "vcc_1v8_s3";
+
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ regulator-suspend-microvolt = <1800000>;
+ };
+ };
+
+ avcc_1v8_s0: pldo-reg1 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-name = "avcc_1v8_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vcc_1v8_s0: pldo-reg2 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-name = "vcc_1v8_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ regulator-suspend-microvolt = <1800000>;
+ };
+ };
+
+ avdd_1v2_s0: pldo-reg3 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1200000>;
+ regulator-max-microvolt = <1200000>;
+ regulator-name = "avdd_1v2_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vcc_3v3_s0: pldo-reg4 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-name = "vcc_3v3_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vccio_sd_s0: pldo-reg5 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-name = "vccio_sd_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ pldo6_s3: pldo-reg6 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-name = "pldo6_s3";
+
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ regulator-suspend-microvolt = <1800000>;
+ };
+ };
+
+ vdd_0v75_s3: nldo-reg1 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <750000>;
+ regulator-max-microvolt = <750000>;
+ regulator-name = "vdd_0v75_s3";
+
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ regulator-suspend-microvolt = <750000>;
+ };
+ };
+
+ vdd_ddr_pll_s0: nldo-reg2 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <850000>;
+ regulator-max-microvolt = <850000>;
+ regulator-name = "vdd_ddr_pll_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ regulator-suspend-microvolt = <850000>;
+ };
+ };
+
+ avdd_0v75_s0: nldo-reg3 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <837500>;
+ regulator-max-microvolt = <837500>;
+ regulator-name = "avdd_0v75_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_0v85_s0: nldo-reg4 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <850000>;
+ regulator-max-microvolt = <850000>;
+ regulator-name = "vdd_0v85_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_0v75_s0: nldo-reg5 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <750000>;
+ regulator-max-microvolt = <750000>;
+ regulator-name = "vdd_0v75_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+ };
+ };
+};
+
+&pinctrl {
+ hym8563 {
+ rtc_int: rtc-int {
+ rockchip,pins =
+ <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+ };
+};
+
+&saradc {
+ vref-supply = <&avcc_1v8_s0>;
+ status = "okay";
+};
+
+&tsadc {
+ status = "okay";
+};
+
+&uart2 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&uart2m0_xfer>;
+ status = "okay";
+};
--
2.47.3
^ permalink raw reply related
* [PATCH 0/2] arm64: dts: rockchip: Add Vicharak Vaaman2 board support
From: Hrushiraj Gandhi @ 2026-06-27 9:48 UTC (permalink / raw)
To: linux-rockchip, devicetree, linux-arm-kernel, linux-kernel
Cc: heiko, robh, krzk+dt, conor+dt, Hrushiraj Gandhi
This patch series adds device tree support for the Vicharak Vaaman2,
a single-board computer based on the Rockchip RK3588 SoC.
The Vaaman2 board features include:
- RK3588 SoC
- eMMC storage and microSD support
- RK806 PMIC and multiple RK8602/RK8603 regulators
- Real-time clock and status LED
- UART serial console and SARADC
Note: This series was generated on top of the pending Vicharak Axon
board support patches (v7) and includes the Axon entry in the context
of the YAML binding patch.
Patch 1 adds the binding documentation for the Vaaman2 board.
Patch 2 adds the actual device tree file and Makefile entry.
Hrushiraj Gandhi (2):
dt-bindings: arm: rockchip: Add Vicharak Vaaman2
arm64: dts: rockchip: Add Vicharak Vaaman2 board
.../devicetree/bindings/arm/rockchip.yaml | 5 +
arch/arm64/boot/dts/rockchip/Makefile | 1 +
.../dts/rockchip/rk3588-vicharak-vaaman2.dts | 547 ++++++++++++++++++
3 files changed, 553 insertions(+)
create mode 100644 arch/arm64/boot/dts/rockchip/rk3588-vicharak-vaaman2.dts
--
2.47.3
^ permalink raw reply
* [PATCH 1/2] dt-bindings: arm: rockchip: Add Vicharak Vaaman2
From: Hrushiraj Gandhi @ 2026-06-27 9:48 UTC (permalink / raw)
To: linux-rockchip, devicetree, linux-arm-kernel, linux-kernel
Cc: heiko, robh, krzk+dt, conor+dt, Hrushiraj Gandhi
In-Reply-To: <20260627094854.81564-1-hrushirajg23@gmail.com>
Add device tree binding documentation for the Vicharak Vaaman2,
a single-board computer based on the Rockchip RK3588 SoC.
Signed-off-by: Hrushiraj Gandhi <hrushirajg23@gmail.com>
---
Documentation/devicetree/bindings/arm/rockchip.yaml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Documentation/devicetree/bindings/arm/rockchip.yaml b/Documentation/devicetree/bindings/arm/rockchip.yaml
index b023d4cc9842..b5a43fd5e546 100644
--- a/Documentation/devicetree/bindings/arm/rockchip.yaml
+++ b/Documentation/devicetree/bindings/arm/rockchip.yaml
@@ -1311,6 +1311,11 @@ properties:
- const: vicharak,axon
- const: rockchip,rk3588
+ - description: Vicharak Vaaman2
+ items:
+ - const: vicharak,vaaman2
+ - const: rockchip,rk3588
+
- description: WolfVision PF5 mainboard
items:
- const: wolfvision,rk3568-pf5
--
2.47.3
^ permalink raw reply related
* [PATCH 2/2] arm64: dts: rockchip: Add Vicharak Vaaman2 board
From: Hrushiraj Gandhi @ 2026-06-27 9:48 UTC (permalink / raw)
To: linux-rockchip, devicetree, linux-arm-kernel, linux-kernel
Cc: heiko, robh, krzk+dt, conor+dt, Hrushiraj Gandhi
In-Reply-To: <20260627094854.81564-1-hrushirajg23@gmail.com>
Add device tree for the Vicharak Vaaman2, a single-board computer
based on the Rockchip RK3588 SoC.
The board features:
- RK3588 SoC with 4x Cortex-A76 (big) + 4x Cortex-A55 (little)
- eMMC storage via SDHCI (HS400)
- microSD card via SDMMC
- RK806 PMIC on SPI2 providing all required power domains
- Two RK8602/RK8603 CPU regulators on I2C0 (big clusters)
- RK8602 NPU regulator on I2C1
- HYM8563 RTC on I2C0
- Status LED on GPIO2_C5 (active-low, heartbeat trigger)
- UART2 as serial console at 1500000 baud
- SARADC with 1.8V reference
Signed-off-by: Hrushiraj Gandhi <hrushirajg23@gmail.com>
---
arch/arm64/boot/dts/rockchip/Makefile | 1 +
.../dts/rockchip/rk3588-vicharak-vaaman2.dts | 547 ++++++++++++++++++
2 files changed, 548 insertions(+)
create mode 100644 arch/arm64/boot/dts/rockchip/rk3588-vicharak-vaaman2.dts
diff --git a/arch/arm64/boot/dts/rockchip/Makefile b/arch/arm64/boot/dts/rockchip/Makefile
index 92114cb757cc..9213280d831b 100644
--- a/arch/arm64/boot/dts/rockchip/Makefile
+++ b/arch/arm64/boot/dts/rockchip/Makefile
@@ -214,6 +214,7 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-tiger-haikou-video-demo.dtbo
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-toybrick-x0.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-turing-rk1.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-vicharak-axon.dtb
+dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-vicharak-vaaman2.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-coolpi-4b.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-evb1-v10.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-gameforce-ace.dtb
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-vicharak-vaaman2.dts b/arch/arm64/boot/dts/rockchip/rk3588-vicharak-vaaman2.dts
new file mode 100644
index 000000000000..9ba9688c1213
--- /dev/null
+++ b/arch/arm64/boot/dts/rockchip/rk3588-vicharak-vaaman2.dts
@@ -0,0 +1,547 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+
+/dts-v1/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/linux-event-codes.h>
+#include <dt-bindings/leds/common.h>
+#include <dt-bindings/pinctrl/rockchip.h>
+#include <dt-bindings/soc/rockchip,vop2.h>
+#include "rk3588.dtsi"
+
+/ {
+ model = "Vicharak Vaaman2";
+ compatible = "vicharak,vaaman2", "rockchip,rk3588";
+
+ aliases {
+ mmc0 = &sdmmc;
+ mmc1 = &sdhci;
+ serial2 = &uart2;
+ };
+
+ chosen {
+ stdout-path = "serial2:1500000n8";
+ };
+
+ leds {
+ compatible = "gpio-leds";
+
+ status_led: status-led {
+ gpios = <&gpio2 RK_PC5 GPIO_ACTIVE_LOW>;
+ function = LED_FUNCTION_STATUS;
+ default-state = "on";
+ retain-state-suspended;
+ linux,default-trigger = "heartbeat";
+ };
+
+ };
+
+ vcc20v_dcin: regulator-vcc20v-dcin {
+ compatible = "regulator-fixed";
+ regulator-name = "vcc20v_dcin";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <20000000>;
+ regulator-max-microvolt = <20000000>;
+ };
+
+ vcc5v0_sys: regulator-vcc5v0-sys {
+ compatible = "regulator-fixed";
+ regulator-name = "vcc5v0_sys";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <5000000>;
+ regulator-max-microvolt = <5000000>;
+ vin-supply = <&vcc20v_dcin>;
+ };
+
+ vcc_1v1_nldo_s3: regulator-vcc-1v1-nldo-s3 {
+ compatible = "regulator-fixed";
+ regulator-name = "vcc_1v1_nldo_s3";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1100000>;
+ regulator-max-microvolt = <1100000>;
+ vin-supply = <&vcc5v0_sys>;
+ };
+
+};
+
+&cpu_b0 {
+ cpu-supply = <&vdd_cpu_big0_s0>;
+};
+
+&cpu_b1 {
+ cpu-supply = <&vdd_cpu_big0_s0>;
+};
+
+&cpu_b2 {
+ cpu-supply = <&vdd_cpu_big1_s0>;
+};
+
+&cpu_b3 {
+ cpu-supply = <&vdd_cpu_big1_s0>;
+};
+
+&cpu_l0 {
+ cpu-supply = <&vdd_cpu_lit_s0>;
+};
+
+&cpu_l1 {
+ cpu-supply = <&vdd_cpu_lit_s0>;
+};
+
+&cpu_l2 {
+ cpu-supply = <&vdd_cpu_lit_s0>;
+};
+
+&cpu_l3 {
+ cpu-supply = <&vdd_cpu_lit_s0>;
+};
+
+
+&i2c0 {
+ pinctrl-0 = <&i2c0m2_xfer>;
+ pinctrl-names = "default";
+ status = "okay";
+
+ vdd_cpu_big0_s0: regulator@42 {
+ compatible = "rockchip,rk8602";
+ fcs,suspend-voltage-selector = <1>;
+ reg = <0x42>;
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-max-microvolt = <1050000>;
+ regulator-min-microvolt = <550000>;
+ regulator-name = "vdd_cpu_big0_s0";
+ regulator-ramp-delay = <2300>;
+ vin-supply = <&vcc5v0_sys>;
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_cpu_big1_s0: regulator@43 {
+ compatible = "rockchip,rk8603", "rockchip,rk8602";
+ fcs,suspend-voltage-selector = <1>;
+ reg = <0x43>;
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-max-microvolt = <1050000>;
+ regulator-min-microvolt = <550000>;
+ regulator-name = "vdd_cpu_big1_s0";
+ regulator-ramp-delay = <2300>;
+ vin-supply = <&vcc5v0_sys>;
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ hym8563: rtc@51 {
+ compatible = "haoyu,hym8563";
+ reg = <0x51>;
+ #clock-cells = <0>;
+ clock-output-names = "hym8563";
+ interrupt-parent = <&gpio0>;
+ interrupts = <RK_PB0 IRQ_TYPE_LEVEL_LOW>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&rtc_int>;
+ wakeup-source;
+ };
+};
+
+&i2c1 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&i2c1m2_xfer>;
+ status = "okay";
+
+ vdd_npu_s0: regulator@42 {
+ compatible = "rockchip,rk8602";
+ reg = <0x42>;
+ fcs,suspend-voltage-selector = <1>;
+ regulator-name = "vdd_npu_s0";
+ regulator-boot-on;
+ regulator-enable-ramp-delay = <500>;
+ regulator-min-microvolt = <550000>;
+ regulator-max-microvolt = <950000>;
+ regulator-ramp-delay = <2300>;
+ vin-supply = <&vcc5v0_sys>;
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+};
+
+&sdhci {
+ bus-width = <8>;
+ full-pwr-cycle-in-suspend;
+ mmc-hs400-1_8v;
+ mmc-hs400-enhanced-strobe;
+ no-sd;
+ no-sdio;
+ non-removable;
+ vmmc-supply = <&vcc_3v3_s3>;
+ vqmmc-supply = <&vcc_1v8_s3>;
+ status = "okay";
+};
+
+&sdmmc {
+ bus-width = <4>;
+ cap-mmc-highspeed;
+ cap-sd-highspeed;
+ cd-gpios = <&gpio0 RK_PA4 GPIO_ACTIVE_LOW>;
+ disable-wp;
+ max-frequency = <200000000>;
+ no-mmc;
+ no-sdio;
+ sd-uhs-sdr104;
+ vmmc-supply = <&vcc_3v3_s3>;
+ vqmmc-supply = <&vccio_sd_s0>;
+ status = "okay";
+};
+
+&spi2 {
+ assigned-clock-rates = <200000000>;
+ assigned-clocks = <&cru CLK_SPI2>;
+ num-cs = <1>;
+ pinctrl-0 = <&spi2m2_cs0 &spi2m2_pins>;
+ pinctrl-names = "default";
+ status = "okay";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pmic@0 {
+ reg = <0>;
+ compatible = "rockchip,rk806";
+ #gpio-cells = <2>;
+ gpio-controller;
+ interrupt-parent = <&gpio0>;
+ interrupts = <7 IRQ_TYPE_LEVEL_LOW>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pmic_pins>, <&rk806_dvs1_null>,
+ <&rk806_dvs2_null>, <&rk806_dvs3_null>;
+ spi-max-frequency = <1000000>;
+
+ vcc1-supply = <&vcc5v0_sys>;
+ vcc2-supply = <&vcc5v0_sys>;
+ vcc3-supply = <&vcc5v0_sys>;
+ vcc4-supply = <&vcc5v0_sys>;
+ vcc5-supply = <&vcc5v0_sys>;
+ vcc6-supply = <&vcc5v0_sys>;
+ vcc7-supply = <&vcc5v0_sys>;
+ vcc8-supply = <&vcc5v0_sys>;
+ vcc9-supply = <&vcc5v0_sys>;
+ vcc10-supply = <&vcc5v0_sys>;
+ vcc11-supply = <&vcc_2v0_pldo_s3>;
+ vcc12-supply = <&vcc5v0_sys>;
+ vcc13-supply = <&vcc_1v1_nldo_s3>;
+ vcc14-supply = <&vcc_1v1_nldo_s3>;
+ vcca-supply = <&vcc5v0_sys>;
+
+ rk806_dvs1_null: dvs1-null-pins {
+ pins = "gpio_pwrctrl1";
+ function = "pin_fun0";
+ };
+
+ rk806_dvs2_null: dvs2-null-pins {
+ pins = "gpio_pwrctrl2";
+ function = "pin_fun0";
+ };
+
+ rk806_dvs3_null: dvs3-null-pins {
+ pins = "gpio_pwrctrl3";
+ function = "pin_fun0";
+ };
+
+ regulators {
+ vdd_gpu_s0: dcdc-reg1 {
+ regulator-boot-on;
+ regulator-min-microvolt = <550000>;
+ regulator-max-microvolt = <950000>;
+ regulator-ramp-delay = <12500>;
+ regulator-name = "vdd_gpu_s0";
+ regulator-enable-ramp-delay = <400>;
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_cpu_lit_s0: dcdc-reg2 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <550000>;
+ regulator-max-microvolt = <950000>;
+ regulator-ramp-delay = <12500>;
+ regulator-name = "vdd_cpu_lit_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_log_s0: dcdc-reg3 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <675000>;
+ regulator-max-microvolt = <750000>;
+ regulator-ramp-delay = <12500>;
+ regulator-name = "vdd_log_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ regulator-suspend-microvolt = <750000>;
+ };
+ };
+
+ vdd_vdenc_s0: dcdc-reg4 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <550000>;
+ regulator-max-microvolt = <950000>;
+ regulator-ramp-delay = <12500>;
+ regulator-name = "vdd_vdenc_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_ddr_s0: dcdc-reg5 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <675000>;
+ regulator-max-microvolt = <900000>;
+ regulator-ramp-delay = <12500>;
+ regulator-name = "vdd_ddr_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ regulator-suspend-microvolt = <850000>;
+ };
+ };
+
+ vdd2_ddr_s3: dcdc-reg6 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-name = "vdd2_ddr_s3";
+
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ };
+ };
+
+ vcc_2v0_pldo_s3: dcdc-reg7 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <2000000>;
+ regulator-max-microvolt = <2000000>;
+ regulator-name = "vdd_2v0_pldo_s3";
+
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ regulator-suspend-microvolt = <2000000>;
+ };
+ };
+
+ vcc_3v3_s3: dcdc-reg8 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-name = "vcc_3v3_s3";
+
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ regulator-suspend-microvolt = <3300000>;
+ };
+ };
+
+ vddq_ddr_s0: dcdc-reg9 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-name = "vddq_ddr_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vcc_1v8_s3: dcdc-reg10 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-name = "vcc_1v8_s3";
+
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ regulator-suspend-microvolt = <1800000>;
+ };
+ };
+
+ avcc_1v8_s0: pldo-reg1 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-name = "avcc_1v8_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vcc_1v8_s0: pldo-reg2 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-name = "vcc_1v8_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ regulator-suspend-microvolt = <1800000>;
+ };
+ };
+
+ avdd_1v2_s0: pldo-reg3 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1200000>;
+ regulator-max-microvolt = <1200000>;
+ regulator-name = "avdd_1v2_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vcc_3v3_s0: pldo-reg4 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-name = "vcc_3v3_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vccio_sd_s0: pldo-reg5 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-name = "vccio_sd_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ pldo6_s3: pldo-reg6 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-name = "pldo6_s3";
+
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ regulator-suspend-microvolt = <1800000>;
+ };
+ };
+
+ vdd_0v75_s3: nldo-reg1 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <750000>;
+ regulator-max-microvolt = <750000>;
+ regulator-name = "vdd_0v75_s3";
+
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ regulator-suspend-microvolt = <750000>;
+ };
+ };
+
+ vdd_ddr_pll_s0: nldo-reg2 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <850000>;
+ regulator-max-microvolt = <850000>;
+ regulator-name = "vdd_ddr_pll_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ regulator-suspend-microvolt = <850000>;
+ };
+ };
+
+ avdd_0v75_s0: nldo-reg3 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <837500>;
+ regulator-max-microvolt = <837500>;
+ regulator-name = "avdd_0v75_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_0v85_s0: nldo-reg4 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <850000>;
+ regulator-max-microvolt = <850000>;
+ regulator-name = "vdd_0v85_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_0v75_s0: nldo-reg5 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <750000>;
+ regulator-max-microvolt = <750000>;
+ regulator-name = "vdd_0v75_s0";
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+ };
+ };
+};
+
+&pinctrl {
+ hym8563 {
+ rtc_int: rtc-int {
+ rockchip,pins =
+ <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+ };
+};
+
+&saradc {
+ vref-supply = <&avcc_1v8_s0>;
+ status = "okay";
+};
+
+&tsadc {
+ status = "okay";
+};
+
+&uart2 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&uart2m0_xfer>;
+ status = "okay";
+};
--
2.47.3
^ permalink raw reply related
* [PATCH v4 0/2] iio: magnetometer: add support for Melexis MLX90393
From: Nikhil Gautam @ 2026-06-27 9:55 UTC (permalink / raw)
To: linux-iio
Cc: jic23, dlechner, nuno.sa, andy, u.kleine-koenig, robh, krzk+dt,
conor+dt, devicetree, linux-kernel, Nikhil Gautam
Hi,
This series adds initial Industrial I/O subsystem support for the
Melexis MLX90393 3-axis magnetometer and temperature sensor.
The MLX90393 supports both I2C and SPI interfaces. This series
implements support for the I2C interface while keeping the driver
structure transport-independent to simplify future SPI support.
The device uses a command-based communication protocol rather than a
conventional register-addressed interface. A small transport abstraction
layer is therefore used instead of regmap to share the common sensor
logic between the current I2C implementation and future SPI support.
Currently supported features:
* Raw magnetic field measurements for X/Y/Z axes
* Raw temperature measurements
* Configurable gain/scale selection
* Configurable oversampling ratio
* Direct mode operation through the IIO subsystem
* I2C interface support
The driver has been tested on Raspberry Pi 5 hardware using an
MLX90393 sensor connected over I2C. Magnetic field and temperature
measurements were verified through the IIO sysfs interface.
Datasheet:
https://www.melexis.com/en/documents/documentation/datasheets/datasheet-mlx90393
Previous submission:
v3: https://lore.kernel.org/linux-iio/20260627005843.7786-3-nikhilgtr@gmail.com/T/#u
v2: https://lore.kernel.org/linux-iio/20260618160141.11409-1-nikhilgtr@gmail.com/T/#t
v1: https://lore.kernel.org/linux-iio/20260510191010.155380-1-nikhilgtr@gmail.com/
Changes in v4:
[DT]
* Updated the example to include the required vdd-supply and
vddio-supply properties.
* Added fixed regulator nodes to the example so it passes dt_binding_check.
[IIO]
* No Change
Changes in v3:
[DT]
* Made vdd-supply and vddio-supply required properties.
* Moved the MAINTAINERS entry to the correct alphabetical position.
[IIO]
* Added the publicly available MLX90393 datasheet reference.
* Clarified the motivation for introducing a dedicated MLX90393 driver.
* Added the I2C device ID table and switched to designated initializers.
* Added a forward declaration for struct device to reduce unnecessary
header dependencies.
* Added symbol namespaces.
* Improved include usage.
* Improved status validation and consistency of conditional checks.
* Added comments documenting protocol encoding, initialization delays,
and temperature conversion constants derived from the datasheet.
* Improved naming, formatting, indentation and general coding style.
* Addressed all review comments from Andy Shevchenko,
Uwe Kleine-König and Krzysztof Kozlowski.
Changes in v2:
[DT]
* Extended the DT binding to document power supply regulators and
optional interrupt and trigger GPIOs.
[IIO]
* Removed the RFC tag based on reviewer feedback.
* Added a MAINTAINERS entry as part of the initial submission and
expanded it in the driver patch.
* Reworked the scale availability implementation to simplify the
data layout and eliminate the need for constructing a temporary
table, avoiding potential race conditions.
* Replaced usleep_range() with fsleep() where appropriate and
documented initialization delays.
* Simplified helper functions and improved error handling by
returning directly where appropriate.
* Reduced unnecessary local variables and line wrapping to improve
readability and align with kernel coding style.
* Added comments for lock protection and command definitions to
improve code clarity.
* Switched to devm_mutex_init() and cleaned up include usage in
accordance with the "include what you use" principle.
* Improved consistency across the driver, including conditional
handling, switch statements, formatting and general code style.
* Addressed all review comments from Jonathan Cameron.
Many thanks to Jonathan Cameron, Andy Shevchenko,
Uwe Kleine-König and Krzysztof Kozlowski for their detailed reviews
and valuable feedback, which significantly improved this series.
Further review and comments are greatly appreciated.
Thanks,
Nikhil Gautam
Nikhil Gautam (2):
dt-bindings: iio: magnetometer: add Melexis MLX90393
iio: magnetometer: add support for Melexis MLX90393
.../iio/magnetometer/melexis,mlx90393.yaml | 63 ++
MAINTAINERS | 7 +
drivers/iio/magnetometer/Kconfig | 10 +
drivers/iio/magnetometer/Makefile | 2 +
drivers/iio/magnetometer/mlx90393.h | 73 ++
drivers/iio/magnetometer/mlx90393_core.c | 701 ++++++++++++++++++
drivers/iio/magnetometer/mlx90393_i2c.c | 76 ++
7 files changed, 932 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/magnetometer/melexis,mlx90393.yaml
create mode 100644 drivers/iio/magnetometer/mlx90393.h
create mode 100644 drivers/iio/magnetometer/mlx90393_core.c
create mode 100644 drivers/iio/magnetometer/mlx90393_i2c.c
--
2.39.5
^ permalink raw reply
* [PATCH v4 1/2] dt-bindings: iio: magnetometer: add Melexis MLX90393
From: Nikhil Gautam @ 2026-06-27 9:55 UTC (permalink / raw)
To: linux-iio
Cc: jic23, dlechner, nuno.sa, andy, u.kleine-koenig, robh, krzk+dt,
conor+dt, devicetree, linux-kernel, Nikhil Gautam
In-Reply-To: <20260627095519.8377-1-nikhilgtr@gmail.com>
Add devicetree bindings for the Melexis MLX90393
3-axis magnetometer and temperature sensor.
The device supports magnetic field and temperature
measurements over I2C and SPI interfaces.
This initial binding documents the I2C interface.
Signed-off-by: Nikhil Gautam <nikhilgtr@gmail.com>
---
.../iio/magnetometer/melexis,mlx90393.yaml | 63 +++++++++++++++++++
MAINTAINERS | 6 ++
2 files changed, 69 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/magnetometer/melexis,mlx90393.yaml
diff --git a/Documentation/devicetree/bindings/iio/magnetometer/melexis,mlx90393.yaml b/Documentation/devicetree/bindings/iio/magnetometer/melexis,mlx90393.yaml
new file mode 100644
index 000000000000..91e984827786
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/magnetometer/melexis,mlx90393.yaml
@@ -0,0 +1,63 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/magnetometer/melexis,mlx90393.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Melexis MLX90393 magnetometer sensor
+
+maintainers:
+ - Nikhil Gautam <nikhilgtr@gmail.com>
+
+description:
+ Melexis MLX90393 3-axis magnetometer and temperature sensor.
+
+properties:
+ compatible:
+ const: melexis,mlx90393
+
+ reg:
+ maxItems: 1
+
+ vdd-supply:
+ description: Regulator that provides power to the sensor
+
+ vddio-supply:
+ description: Regulator that provides power to the bus
+
+ interrupts:
+ maxItems: 1
+
+ trigger-gpios:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - vdd-supply
+ - vddio-supply
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ magnetometer@c {
+ compatible = "melexis,mlx90393";
+ reg = <0x0c>;
+
+ interrupt-parent = <&gpio>;
+ interrupts = <17 IRQ_TYPE_EDGE_RISING>;
+
+ trigger-gpios = <&gpio 18 GPIO_ACTIVE_HIGH>;
+
+ vdd-supply = <&vdd>;
+ vddio-supply = <&vddio>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index a92290fffa16..469ae3f9aa0f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15501,6 +15501,12 @@ F: Documentation/scsi/megaraid.rst
F: drivers/scsi/megaraid.*
F: drivers/scsi/megaraid/
+MELEXIS MLX90393 MAGNETOMETER DRIVER
+M: Nikhil Gautam <nikhilgtr@gmail.com>
+L: linux-iio@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/iio/magnetometer/melexis,mlx90393.yaml
+
MELEXIS MLX90614 DRIVER
M: Crt Mori <cmo@melexis.com>
L: linux-iio@vger.kernel.org
--
2.39.5
^ permalink raw reply related
* [PATCH v4 2/2] iio: magnetometer: add support for Melexis MLX90393
From: Nikhil Gautam @ 2026-06-27 9:55 UTC (permalink / raw)
To: linux-iio
Cc: jic23, dlechner, nuno.sa, andy, u.kleine-koenig, robh, krzk+dt,
conor+dt, devicetree, linux-kernel, Nikhil Gautam
In-Reply-To: <20260627095519.8377-1-nikhilgtr@gmail.com>
Add Industrial I/O subsystem support for the Melexis
MLX90393 3-axis magnetometer and temperature sensor.
The driver currently supports:
raw magnetic field measurements
raw temperature measurements
configurable gain/scale selection
configurable oversampling ratio
direct mode operation
The MLX90393 supports both I2C and SPI interfaces. This
initial implementation adds support for the I2C interface.
The device uses a command-based communication protocol
rather than a conventional register-addressed interface.
A small transport abstraction layer is therefore used
instead of regmap to share the common sensor logic
between the current I2C implementation and future SPI
support without duplicating code.
Signed-off-by: Nikhil Gautam <nikhilgtr@gmail.com>
---
MAINTAINERS | 1 +
drivers/iio/magnetometer/Kconfig | 10 +
drivers/iio/magnetometer/Makefile | 2 +
drivers/iio/magnetometer/mlx90393.h | 73 +++
drivers/iio/magnetometer/mlx90393_core.c | 701 +++++++++++++++++++++++
drivers/iio/magnetometer/mlx90393_i2c.c | 76 +++
6 files changed, 863 insertions(+)
create mode 100644 drivers/iio/magnetometer/mlx90393.h
create mode 100644 drivers/iio/magnetometer/mlx90393_core.c
create mode 100644 drivers/iio/magnetometer/mlx90393_i2c.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 469ae3f9aa0f..177dda261801 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15506,6 +15506,7 @@ M: Nikhil Gautam <nikhilgtr@gmail.com>
L: linux-iio@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/iio/magnetometer/melexis,mlx90393.yaml
+F: drivers/iio/magnetometer/mlx90393*
MELEXIS MLX90614 DRIVER
M: Crt Mori <cmo@melexis.com>
diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig
index 3debf1320ad1..e6b74e7e3317 100644
--- a/drivers/iio/magnetometer/Kconfig
+++ b/drivers/iio/magnetometer/Kconfig
@@ -128,6 +128,16 @@ config HID_SENSOR_MAGNETOMETER_3D
Say yes here to build support for the HID SENSOR
Magnetometer 3D.
+config MLX90393
+ tristate "MELEXIS MLX90393 3-axis magnetometer sensor"
+ depends on I2C
+ help
+ Say yes here to build support for the MELEXIS MLX90393 3-axis
+ magnetometer.
+
+ To compile this driver as a module, choose M here: the module
+ will be called mlx90393.
+
config MMC35240
tristate "MEMSIC MMC35240 3-axis magnetic sensor"
select REGMAP_I2C
diff --git a/drivers/iio/magnetometer/Makefile b/drivers/iio/magnetometer/Makefile
index 9297723a97d8..542c89d38a59 100644
--- a/drivers/iio/magnetometer/Makefile
+++ b/drivers/iio/magnetometer/Makefile
@@ -14,6 +14,8 @@ obj-$(CONFIG_BMC150_MAGN_SPI) += bmc150_magn_spi.o
obj-$(CONFIG_MAG3110) += mag3110.o
obj-$(CONFIG_HID_SENSOR_MAGNETOMETER_3D) += hid-sensor-magn-3d.o
+obj-$(CONFIG_MLX90393) += mlx90393_core.o
+obj-$(CONFIG_MLX90393) += mlx90393_i2c.o
obj-$(CONFIG_MMC35240) += mmc35240.o
obj-$(CONFIG_IIO_ST_MAGN_3AXIS) += st_magn.o
diff --git a/drivers/iio/magnetometer/mlx90393.h b/drivers/iio/magnetometer/mlx90393.h
new file mode 100644
index 000000000000..6a646ac05198
--- /dev/null
+++ b/drivers/iio/magnetometer/mlx90393.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * MLX90393 magnetometer & temperature sensor driver
+ *
+ * Copyright (c) 2026 Nikhil Gautam <nikhilgtr@gmail.com>
+ */
+
+#ifndef MLX90393_H
+#define MLX90393_H
+
+#include <linux/bits.h>
+#include <linux/types.h>
+
+#define MLX90393_AXIS_MAX 2
+#define MLX90393_GAIN_MAX 8
+#define MLX90393_RES_MAX 4
+#define MLX90393_OSR2_MAX 4
+#define MLX90393_OSR_MAX 4
+
+#define MLX90393_CMD_MASK GENMASK(7, 4)
+
+/* Commands (datasheet, Table 11 - Command List) */
+#define MLX90393_CMD_SB 0x10 /* Start Burst Mode */
+#define MLX90393_CMD_SW 0x20 /* Start Wake-up on Change Mode */
+#define MLX90393_CMD_SM 0x30 /* Start Single Measurement Mode */
+#define MLX90393_CMD_RM 0x40 /* Read Measurement */
+#define MLX90393_CMD_RR 0x50 /* Read Register */
+#define MLX90393_CMD_WR 0x60 /* Write Register */
+#define MLX90393_CMD_EX 0x80 /* Exit Mode */
+#define MLX90393_CMD_HR 0xD0 /* Memory Recall */
+#define MLX90393_CMD_HS 0xE0 /* Memory Store */
+#define MLX90393_CMD_RT 0xF0 /* Reset Device */
+
+#define MLX90393_MEASURE_Z BIT(0)
+#define MLX90393_MEASURE_Y BIT(1)
+#define MLX90393_MEASURE_X BIT(2)
+#define MLX90393_MEASURE_TEMP BIT(3)
+
+#define MLX90393_MEASURE_ALL (MLX90393_MEASURE_TEMP | \
+ MLX90393_MEASURE_X | MLX90393_MEASURE_Y | MLX90393_MEASURE_Z)
+
+#define MLX90393_NUM_CHANNELS 4
+
+#define MLX90393_STATUS_RESP GENMASK(1, 0)
+#define MLX90393_STATUS_RT BIT(2)
+#define MLX90393_STATUS_ERROR BIT(4)
+
+#define MLX90393_REG_CONF1 0x00
+#define MLX90393_REG_CONF2 0x01
+#define MLX90393_REG_CONF3 0x02
+#define MLX90393_REG_CONF4 0x03
+
+#define MLX90393_CONF1_GAIN_SEL GENMASK(6, 4)
+#define MLX90393_CONF1_HALLCONF GENMASK(3, 0)
+
+#define MLX90393_CONF3_OSR GENMASK(1, 0)
+#define MLX90393_CONF3_DIG_FILT GENMASK(4, 2)
+#define MLX90393_CONF3_RES_X GENMASK(6, 5)
+#define MLX90393_CONF3_RES_Y GENMASK(8, 7)
+#define MLX90393_CONF3_RES_Z GENMASK(10, 9)
+#define MLX90393_CONF3_OSR2 GENMASK(12, 11)
+
+struct device;
+
+struct mlx90393_transfer_ops {
+ int (*xfer)(void *context, const u8 *tx, int tx_len, u8 *rx, int rx_len);
+};
+
+int mlx90393_core_probe(struct device *dev,
+ const struct mlx90393_transfer_ops *ops,
+ void *context);
+
+#endif
diff --git a/drivers/iio/magnetometer/mlx90393_core.c b/drivers/iio/magnetometer/mlx90393_core.c
new file mode 100644
index 000000000000..78fcab8a2b56
--- /dev/null
+++ b/drivers/iio/magnetometer/mlx90393_core.c
@@ -0,0 +1,701 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MLX90393 magnetometer & temperature sensor driver
+ *
+ * Copyright (c) 2026 Nikhil Gautam <nikhilgtr@gmail.com>
+ *
+ * Datasheet: https://media.melexis.com/-/media/files/documents/datasheets/mlx90393-datasheet-melexis.pdf
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/time64.h>
+#include <linux/unaligned.h>
+#include <linux/units.h>
+
+#include <linux/iio/iio.h>
+
+#include "mlx90393.h"
+
+struct mlx90393_data {
+ /* Protects sensor configuration and measurement operations */
+ struct mutex lock;
+ struct device *dev;
+ void *bus_context;
+ const struct mlx90393_transfer_ops *ops;
+ u8 gain_sel;
+ u8 hallconf;
+
+ u8 res_xy;
+ u8 res_z;
+
+ u8 dig_filt;
+ u8 osr;
+ u8 osr2;
+};
+
+enum mlx90393_channels {
+ MLX90393_CHAN_X,
+ MLX90393_CHAN_Y,
+ MLX90393_CHAN_Z,
+ MLX90393_CHAN_TEMP,
+};
+
+enum mlx90393_axis_type {
+ MLX90393_AXIS_TYPE_XY,
+ MLX90393_AXIS_TYPE_Z,
+};
+
+/* Datasheet: Table no.17 */
+static const int mlx90393_scale_table[MLX90393_AXIS_MAX][MLX90393_GAIN_MAX]
+ [MLX90393_RES_MAX] = {
+ /* XY axis */
+ {
+ { 751, 1502, 3004, 6009},
+ { 601, 1202, 2403, 4840},
+ { 451, 901, 1803, 3605},
+ { 376, 751, 1502, 3004},
+ { 300, 601, 1202, 2403},
+ { 250, 501, 1001, 2003},
+ { 200, 401, 801, 1602},
+ { 150, 300, 601, 1202},
+ },
+ /* Z axis */
+ {
+ { 1210, 2420, 4840, 9680},
+ { 968, 1936, 3872, 7744},
+ { 726, 1452, 2904, 5808},
+ { 605, 1210, 2420, 4840},
+ { 484, 968, 1936, 3872},
+ { 403, 807, 1613, 3227},
+ { 323, 645, 1291, 2581},
+ { 242, 484, 968, 1936},
+ }
+};
+
+static const int mlx90393_osr2_avail[MLX90393_OSR2_MAX] = {
+ 0, 1, 2, 3,
+};
+
+static const int mlx90393_osr_avail[MLX90393_OSR_MAX] = {
+ 1, 2, 4, 8,
+};
+
+#define MLX90393_CHAN(idx, axis, addr) { \
+ .type = IIO_MAGN, \
+ .modified = 1, \
+ .channel = idx, \
+ .address = addr, \
+ .channel2 = IIO_MOD_##axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),\
+ .info_mask_separate_available = \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_type_available = \
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
+}
+
+static const struct iio_chan_spec mlx90393_channels[] = {
+ MLX90393_CHAN(0, X, MLX90393_CHAN_X),
+ MLX90393_CHAN(1, Y, MLX90393_CHAN_Y),
+ MLX90393_CHAN(2, Z, MLX90393_CHAN_Z),
+ {
+ .type = IIO_TEMP,
+ .address = MLX90393_CHAN_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .info_mask_separate_available =
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
+ },
+};
+
+/*
+ * Calculate total conversion time in microseconds.
+ *
+ * Formula derived from datasheet timing equations.
+ * Datasheet: Table 8, Page no. 12
+ *
+ * Abbreviations:
+ * TCONVM -> Single Magnetic axis conversion time
+ * TCONVT -> Temperature conversion time
+ * TSTBY -> Time from IDLE to STANDBY
+ * TACTIVE -> Time from STBY to ACTIVE
+ * TCONV_END -> Time to end analog active mode
+ */
+static int mlx90393_get_tconv_us(struct mlx90393_data *data)
+{
+ const int osr = data->osr;
+ const int osr2 = data->osr2;
+ const int df = data->dig_filt;
+
+ int tconvm;
+ int tconvt;
+
+ int m = 3; /* X,Y,Z */
+
+ /*
+ * TCONVM = 67 + 64 * 2^OSR * (2 + 2^DIG_FILT)
+ */
+ tconvm = 67 + (64 * BIT(osr) * (2 + BIT(df)));
+
+ /*
+ * TCONVT = 67 + 192 * 2^OSR2
+ */
+ tconvt = 67 + (192 * BIT(osr2));
+ /*
+ * Total conversion time:
+ * TSTBY + TACTIVE + m * TCONVM + TCONVT + TCONV_END
+ */
+ return 220 + 360 + (m * tconvm) + tconvt + 120;
+}
+
+static int mlx90393_xfer(struct mlx90393_data *data,
+ const u8 *tx, int tx_len,
+ u8 *rx, int rx_len)
+{
+ return data->ops->xfer(data->bus_context, tx, tx_len, rx, rx_len);
+}
+
+static int mlx90393_check_status(u8 cmd, u8 status)
+{
+ /* Datasheet: Table 12: Status byte definition */
+
+ /* Always validate error bit */
+ if (status & MLX90393_STATUS_ERROR)
+ return -EIO;
+
+ switch (cmd & MLX90393_CMD_MASK) {
+ case MLX90393_CMD_RM:
+ /*
+ * D1:D0 indicates response availability
+ * 00 means invalid/no measurement
+ */
+ if ((status & MLX90393_STATUS_RESP) == 0)
+ return -EIO;
+ return 0;
+ case MLX90393_CMD_RT:
+ /* Reset acknowledge */
+ if ((status & MLX90393_STATUS_RT) == 0)
+ return -EIO;
+ return 0;
+ default:
+ return 0;
+ }
+}
+
+static int mlx90393_write_cmd(struct mlx90393_data *data, u8 cmd)
+{
+ u8 status;
+ int ret;
+
+ ret = mlx90393_xfer(data, &cmd, 1, &status, 1);
+ if (ret)
+ return ret;
+
+ return mlx90393_check_status(cmd, status);
+}
+
+static int mlx90393_read_cmd(struct mlx90393_data *data, u8 cmd, u8 *rx,
+ int rx_len)
+{
+ int ret;
+
+ ret = mlx90393_xfer(data, &cmd, 1, rx, rx_len);
+ if (ret)
+ return ret;
+
+ return mlx90393_check_status(cmd, rx[0]);
+}
+
+static int mlx90393_read_reg(struct mlx90393_data *data, u8 reg, u16 *val)
+{
+ u8 tx[2];
+ u8 rx[3];
+ int ret;
+
+ tx[0] = MLX90393_CMD_RR;
+ /* Register address is encoded in bits [7:2] */
+ tx[1] = reg << 2;
+
+ ret = mlx90393_xfer(data, tx, sizeof(tx), rx, sizeof(rx));
+ if (ret)
+ return ret;
+
+ ret = mlx90393_check_status(tx[0], rx[0]);
+ if (ret)
+ return ret;
+
+ *val = get_unaligned_be16(&rx[1]);
+
+ return 0;
+}
+
+static int mlx90393_write_reg(struct mlx90393_data *data, u8 reg, u16 val)
+{
+ u8 tx[4];
+ u8 status;
+ int ret;
+
+ tx[0] = MLX90393_CMD_WR;
+ put_unaligned_be16(val, &tx[1]);
+ /* Register address is encoded in bits [7:2] */
+ tx[3] = reg << 2;
+
+ ret = mlx90393_xfer(data, tx, sizeof(tx), &status, 1);
+ if (ret)
+ return ret;
+
+ return mlx90393_check_status(tx[0], status);
+}
+
+static int mlx90393_update_bits(struct mlx90393_data *data, u8 reg,
+ u16 mask, u16 val)
+{
+ u16 reg_val;
+ int ret;
+
+ ret = mlx90393_read_reg(data, reg, ®_val);
+ if (ret)
+ return ret;
+
+ reg_val &= ~mask;
+ reg_val |= (val << __ffs(mask)) & mask;
+
+ return mlx90393_write_reg(data, reg, reg_val);
+}
+
+static int mlx90393_read_measurement(struct mlx90393_data *data,
+ enum mlx90393_channels chan, int *val)
+{
+ u8 rx[9];
+ int ret;
+
+ /* Start measurement */
+ ret = mlx90393_write_cmd(data, MLX90393_CMD_SM | MLX90393_MEASURE_ALL);
+ if (ret)
+ return ret;
+
+ /* Wait conversion */
+ fsleep(mlx90393_get_tconv_us(data));
+
+ /* Read measurement */
+ ret = mlx90393_read_cmd(data, MLX90393_CMD_RM | MLX90393_MEASURE_ALL,
+ rx, sizeof(rx));
+ if (ret)
+ return ret;
+ /*
+ * Measurement response layout:
+ * [status][temp][x][y][z]
+ */
+
+ switch (chan) {
+ case MLX90393_CHAN_TEMP:
+ *val = get_unaligned_be16(&rx[1]);
+ return 0;
+
+ case MLX90393_CHAN_X:
+ *val = sign_extend32(get_unaligned_be16(&rx[3]), 15);
+ return 0;
+
+ case MLX90393_CHAN_Y:
+ *val = sign_extend32(get_unaligned_be16(&rx[5]), 15);
+ return 0;
+
+ case MLX90393_CHAN_Z:
+ *val = sign_extend32(get_unaligned_be16(&rx[7]), 15);
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mlx90393_get_scale(struct mlx90393_data *data,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2)
+{
+ enum mlx90393_axis_type axis;
+ u8 res;
+
+ if (chan->channel2 == IIO_MOD_Z) {
+ axis = MLX90393_AXIS_TYPE_Z;
+ res = data->res_z;
+ } else {
+ axis = MLX90393_AXIS_TYPE_XY;
+ res = data->res_xy;
+ }
+
+ *val = 0;
+ *val2 = mlx90393_scale_table[axis][data->gain_sel][res];
+
+ return IIO_VAL_INT_PLUS_NANO;
+}
+
+static int mlx90393_find_scale(struct mlx90393_data *data, bool z_axis,
+ int val, int val2,
+ int *gain)
+{
+ u8 res;
+ enum mlx90393_axis_type axis;
+
+ if (z_axis) {
+ axis = MLX90393_AXIS_TYPE_Z;
+ res = data->res_z;
+ } else {
+ axis = MLX90393_AXIS_TYPE_XY;
+ res = data->res_xy;
+ }
+
+ if (val != 0)
+ return -EINVAL;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(mlx90393_scale_table[0]); i++) {
+ if (mlx90393_scale_table[axis][i][res] == val2) {
+ *gain = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int mlx90393_set_scale(struct mlx90393_data *data,
+ const struct iio_chan_spec *chan,
+ int val, int val2)
+{
+ bool z_axis;
+ int gain;
+ int ret;
+
+ z_axis = chan->channel2 == IIO_MOD_Z;
+
+ ret = mlx90393_find_scale(data, z_axis, val, val2, &gain);
+ if (ret)
+ return ret;
+
+ ret = mlx90393_update_bits(data, MLX90393_REG_CONF1, MLX90393_CONF1_GAIN_SEL,
+ gain);
+ if (ret)
+ return ret;
+
+ data->gain_sel = gain;
+ return 0;
+}
+
+static int mlx90393_get_osr(struct mlx90393_data *data, int *val)
+{
+ *val = mlx90393_osr_avail[data->osr];
+ return IIO_VAL_INT;
+}
+
+static int mlx90393_find_osr(int val, int *osr)
+{
+ for (unsigned int i = 0; i < MLX90393_OSR_MAX; i++) {
+ if (mlx90393_osr_avail[i] == val) {
+ *osr = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int mlx90393_get_temp_osr2(struct mlx90393_data *data, int *val)
+{
+ *val = mlx90393_osr2_avail[data->osr2];
+ return IIO_VAL_INT;
+}
+
+static int mlx90393_set_osr(struct mlx90393_data *data, int val)
+{
+ int osr;
+ int ret;
+
+ ret = mlx90393_find_osr(val, &osr);
+ if (ret)
+ return ret;
+
+ if (osr == data->osr)
+ return 0;
+
+ ret = mlx90393_update_bits(data, MLX90393_REG_CONF3, MLX90393_CONF3_OSR,
+ osr);
+ if (ret)
+ return ret;
+
+ data->osr = osr;
+ return 0;
+}
+
+static int mlx90393_set_temp_osr2(struct mlx90393_data *data, int val)
+{
+ int ret;
+
+ if (val < 0 || val >= MLX90393_OSR2_MAX)
+ return -EINVAL;
+
+ if (val == data->osr2)
+ return 0;
+
+ ret = mlx90393_update_bits(data, MLX90393_REG_CONF3, MLX90393_CONF3_OSR2,
+ val);
+ if (ret)
+ return ret;
+
+ data->osr2 = val;
+
+ return 0;
+}
+
+static int mlx90393_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_INT_PLUS_NANO;
+
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ return IIO_VAL_INT;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mlx90393_write_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int val, int val2,
+ long mask)
+{
+ struct mlx90393_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE: {
+ guard(mutex)(&data->lock);
+ return mlx90393_set_scale(data, chan, val, val2);
+ }
+
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO: {
+ guard(mutex)(&data->lock);
+ switch (chan->type) {
+ case IIO_TEMP:
+ return mlx90393_set_temp_osr2(data, val);
+
+ case IIO_MAGN:
+ return mlx90393_set_osr(data, val);
+
+ default:
+ return -EINVAL;
+ }
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mlx90393_read_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2, long mask)
+{
+ struct mlx90393_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW: {
+ guard(mutex)(&data->lock);
+ ret = mlx90393_read_measurement(data, chan->address, val);
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_MAGN:
+ return mlx90393_get_scale(data, chan, val, val2);
+
+ case IIO_TEMP:
+ /*
+ * Datasheet Table 7: Thermal Specification
+ */
+ *val = 0;
+ *val2 = 22124;
+ return IIO_VAL_INT_PLUS_MICRO;
+
+ default:
+ return -EINVAL;
+ }
+
+ case IIO_CHAN_INFO_OFFSET:
+ if (chan->type != IIO_TEMP)
+ return -EINVAL;
+ /*
+ * Datasheet Table 7: Thermal Specification
+ */
+
+ *val = -45114;
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ switch (chan->type) {
+ case IIO_TEMP:
+ return mlx90393_get_temp_osr2(data, val);
+ case IIO_MAGN:
+ return mlx90393_get_osr(data, val);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mlx90393_read_avail(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ const int **vals,
+ int *type,
+ int *length,
+ long mask)
+{
+ struct mlx90393_data *data = iio_priv(indio_dev);
+ static int scale_avail[MLX90393_GAIN_MAX][MLX90393_AXIS_MAX];
+ enum mlx90393_axis_type axis;
+ u8 res;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE: {
+ guard(mutex)(&data->lock);
+ axis = chan->channel2 == IIO_MOD_Z;
+ res = axis ? data->res_z : data->res_xy;
+
+ for (unsigned int i = 0; i < MLX90393_GAIN_MAX; i++) {
+ scale_avail[i][0] = 0;
+ scale_avail[i][1] =
+ mlx90393_scale_table[axis][i][res];
+ }
+
+ *vals = &scale_avail[0][0];
+ *type = IIO_VAL_INT_PLUS_NANO;
+ *length = MLX90393_GAIN_MAX * MLX90393_AXIS_MAX;
+ return IIO_AVAIL_LIST;
+ }
+
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ if (chan->type == IIO_TEMP) {
+ *vals = mlx90393_osr2_avail;
+ *type = IIO_VAL_INT;
+ *length = MLX90393_OSR2_MAX;
+ } else {
+ *vals = mlx90393_osr_avail;
+ *type = IIO_VAL_INT;
+ *length = MLX90393_OSR_MAX;
+ }
+ return IIO_AVAIL_LIST;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info mlx90393_info = {
+ .read_raw = mlx90393_read_raw,
+ .write_raw = mlx90393_write_raw,
+ .read_avail = mlx90393_read_avail,
+ .write_raw_get_fmt = mlx90393_write_raw_get_fmt,
+};
+
+static int mlx90393_init(struct mlx90393_data *data)
+{
+ int ret;
+ u16 reg;
+
+ /* Exit mode */
+ ret = mlx90393_write_cmd(data, MLX90393_CMD_EX);
+ if (ret)
+ return ret;
+
+ /*
+ * Datasheet section 15.4.1.2 (RT command), Figure 16:
+ * Wait 1 ms after EX command before issuing RT.
+ */
+ fsleep(1 * USEC_PER_MSEC);
+
+ /* Reset device */
+ ret = mlx90393_write_cmd(data, MLX90393_CMD_RT);
+ if (ret)
+ return ret;
+
+ /*
+ * Datasheet section 15.4.1.2 (RT command), Figure 16:
+ * Wait 1.5 ms for the start-up sequence to complete.
+ */
+ fsleep(1.5 * USEC_PER_MSEC);
+
+ ret = mlx90393_read_reg(data, MLX90393_REG_CONF1, ®);
+ if (ret)
+ return ret;
+
+ data->gain_sel = FIELD_GET(MLX90393_CONF1_GAIN_SEL, reg);
+ data->hallconf = FIELD_GET(MLX90393_CONF1_HALLCONF, reg);
+
+ ret = mlx90393_read_reg(data, MLX90393_REG_CONF3, ®);
+ if (ret)
+ return ret;
+
+ data->res_xy = FIELD_GET(MLX90393_CONF3_RES_X, reg);
+ data->res_z = FIELD_GET(MLX90393_CONF3_RES_Z, reg);
+ data->dig_filt = FIELD_GET(MLX90393_CONF3_DIG_FILT, reg);
+ data->osr = FIELD_GET(MLX90393_CONF3_OSR, reg);
+ data->osr2 = FIELD_GET(MLX90393_CONF3_OSR2, reg);
+
+ return 0;
+}
+
+int mlx90393_core_probe(struct device *dev,
+ const struct mlx90393_transfer_ops *ops,
+ void *context)
+{
+ struct iio_dev *indio_dev;
+ struct mlx90393_data *data;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ ret = devm_mutex_init(dev, &data->lock);
+ if (ret)
+ return ret;
+
+ data->dev = dev;
+ data->ops = ops;
+ data->bus_context = context;
+
+ indio_dev->name = "mlx90393";
+ indio_dev->info = &mlx90393_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = mlx90393_channels;
+ indio_dev->num_channels = ARRAY_SIZE(mlx90393_channels);
+
+ ret = mlx90393_init(data);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to initialize device\n");
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+EXPORT_SYMBOL_NS_GPL(mlx90393_core_probe, "IIO_MLX90393");
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nikhil Gautam <nikhilgtr@gmail.com>");
+MODULE_DESCRIPTION("MLX90393 magnetometer sensor driver");
diff --git a/drivers/iio/magnetometer/mlx90393_i2c.c b/drivers/iio/magnetometer/mlx90393_i2c.c
new file mode 100644
index 000000000000..a9f0a40d15e8
--- /dev/null
+++ b/drivers/iio/magnetometer/mlx90393_i2c.c
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/array_size.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+
+#include "mlx90393.h"
+
+/*
+ * MLX90393 commands use repeated-start transfers where
+ * every command is followed by a status/data response.
+ */
+static int mlx90393_i2c_xfer(void *context,
+ const u8 *tx, int tx_len,
+ u8 *rx, int rx_len)
+{
+ struct i2c_client *client = context;
+ int ret;
+ struct i2c_msg msgs[2] = {
+ [0] = {
+ .addr = client->addr,
+ .len = tx_len,
+ .buf = (u8 *)tx,
+ },
+ [1] = {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = rx_len,
+ .buf = rx,
+ },
+ };
+
+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+ if (ret != ARRAY_SIZE(msgs))
+ return ret < 0 ? ret : -EIO;
+
+ return 0;
+}
+
+static const struct mlx90393_transfer_ops mlx90393_i2c_ops = {
+ .xfer = mlx90393_i2c_xfer,
+};
+
+static int mlx90393_i2c_probe(struct i2c_client *client)
+{
+ return mlx90393_core_probe(&client->dev, &mlx90393_i2c_ops, client);
+}
+
+static const struct i2c_device_id mlx90393_id[] = {
+ { .name = "mlx90393" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mlx90393_id);
+
+static const struct of_device_id mlx90393_of_match[] = {
+ { .compatible = "melexis,mlx90393" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mlx90393_of_match);
+
+static struct i2c_driver mlx90393_i2c_driver = {
+ .driver = {
+ .name = "mlx90393",
+ .of_match_table = mlx90393_of_match,
+ },
+ .probe = mlx90393_i2c_probe,
+ .id_table = mlx90393_id,
+};
+
+module_i2c_driver(mlx90393_i2c_driver);
+
+MODULE_IMPORT_NS("IIO_MLX90393");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nikhil Gautam <nikhilgtr@gmail.com>");
+MODULE_DESCRIPTION("MLX90393 magnetometer sensor driver");
--
2.39.5
^ permalink raw reply related
* [PATCH 0/5] arm64: qcom: dts: Add display support for Shikra
From: Nabige Aala @ 2026-06-27 10:01 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: linux-arm-msm, devicetree, linux-kernel, arpit.saini, mohit.dsor,
Nabige Aala
This series adds display support for the Qualcomm Shikra platform.
It introduces the Shikra MDSS display subsystem support at the SoC
level, and enables display on the Shikra CQS EVK, CQM EVK and IQS
EVK boards with the required DSI panel nodes. It also adds LT9611UXD
support for HDMI output.
Nabige Aala (5):
arm64: dts: qcom: shikra: Add MDSS display subsystem
arm64: dts: qcom: shikra-cqm-evk: Enable display and add ili7807s panel
arm64: defconfig: Enable ILI7807S DSI panel driver
arm64: dts: qcom: shikra-cqs-evk: Enable display and add ili7807s panel
arm64: dts: qcom: Shikra LT9611UXD support
arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
arch/arm64/boot/dts/qcom/shikra.dtsi | 203 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
arch/arm64/configs/defconfig | 1 +
5 files changed, 534 insertions(+), 2 deletions(-)
prerequisite-message-id: <20260611-shikra-dt-v5-0-103ed26a8529@oss.qualcomm.com>
prerequisite-message-id: <20260608-shikra-dt-m1-v4-0-2114300594a6@oss.qualcomm.com>
prerequisite-message-id: <20260604-shikra-dispcc-gpucc-v4-0-8204f1029311@oss.qualcomm.com>
prerequisite-message-id: <20260608-shikra-display-v4-0-88a846afdd5d@oss.qualcomm.com>
prerequisite-message-id: <20260618-ili7807s-v2-0-b3f0c109b102@oss.qualcomm.com>
prerequisite-message-id: <20260508134009.4582-1-syyang@lontium.com>
prerequisite-message-id: <20260611-lt9611-b4-send-v1-0-42abbcd3bb1e@oss.qualcomm.com>
To: Bjorn Andersson <andersson@kernel.org>
To: Konrad Dybcio <konradybcio@kernel.org>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
Cc: linux-arm-msm@vger.kernel.org
Cc: devicetree@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: arpit.saini@oss.qualcomm.com
Cc: mohit.dsor@oss.qualcomm.com
Signed-off-by: arpit.saini@oss.qualcomm.com
Signed-off-by: mohit.dsor@oss.qualcomm.com
Signed-off-by: Nabige Aala <nabige.aala@oss.qualcomm.com>
---
Arpit Saini (3):
arm64: dts: qcom: shikra-cqm-evk: Enable display and add ili7807s panel
arm64: defconfig: Enable ILI7807S DSI panel driver
arm64: dts: qcom: shikra-cqs-evk: Enable display and add ili7807s panel
Mohit Dsor (1):
arm64: dts: qcom: Shikra LT9611UXD support
Nabige Aala (1):
arm64: dts: qcom: shikra: Add MDSS display subsystem
arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts | 104 ++++++++++++++
arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts | 104 ++++++++++++++
arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts | 122 +++++++++++++++++
arch/arm64/boot/dts/qcom/shikra.dtsi | 203 +++++++++++++++++++++++++++-
arch/arm64/configs/defconfig | 1 +
5 files changed, 532 insertions(+), 2 deletions(-)
---
base-commit: f28fb4ff0d25819c58341fec6e3953f1ebb1fc2e
change-id: 20260626-shikra-dt-changes-8d127be951d5
Best regards,
--
Nabige Aala <nabige.aala@oss.qualcomm.com>
^ permalink raw reply
* [PATCH 1/5] arm64: dts: qcom: shikra: Add MDSS display subsystem
From: Nabige Aala @ 2026-06-27 10:01 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: linux-arm-msm, devicetree, linux-kernel, arpit.saini, mohit.dsor,
Nabige Aala
In-Reply-To: <20260627-shikra-dt-changes-v1-0-449a402673d0@oss.qualcomm.com>
Add the SoC-level display subsystem nodes for Shikra: MDSS wrapper,
DPU display controller, DSI host controller, and 14nm DSI PHY.
Shikra uses DPU 6.5 hardware (same as QCM2290). Platform-specific
compatible strings are used as the primary match with QCM2290 fallbacks
to reuse the existing driver support.
The dispcc clock inputs for the DSI byte and pixel PLLs are wired
from mdss_dsi0_phy.
Signed-off-by: Nabige Aala <nabige.aala@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/shikra.dtsi | 203 ++++++++++++++++++++++++++++++++++-
1 file changed, 201 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/boot/dts/qcom/shikra.dtsi b/arch/arm64/boot/dts/qcom/shikra.dtsi
index e67fe047a683..3c8eda0a518b 100644
--- a/arch/arm64/boot/dts/qcom/shikra.dtsi
+++ b/arch/arm64/boot/dts/qcom/shikra.dtsi
@@ -4,6 +4,7 @@
*/
#include <dt-bindings/clock/qcom,dispcc-qcm2290.h>
+#include <dt-bindings/clock/qcom,dsi-phy-28nm.h>
#include <dt-bindings/clock/qcom,qcm2290-gpucc.h>
#include <dt-bindings/clock/qcom,rpmcc.h>
#include <dt-bindings/clock/qcom,shikra-gcc.h>
@@ -1990,6 +1991,204 @@ gpucc: clock-controller@5990000 {
#power-domain-cells = <1>;
};
+ mdss: display-subsystem@5e00000 {
+ compatible = "qcom,shikra-mdss", "qcom,qcm2290-mdss";
+ reg = <0x0 0x05e00000 0x0 0x1000>;
+ reg-names = "mdss";
+ interrupts = <GIC_SPI 186 IRQ_TYPE_LEVEL_HIGH 0>;
+ interrupt-controller;
+ #interrupt-cells = <1>;
+
+ clocks = <&gcc GCC_DISP_AHB_CLK>,
+ <&gcc GCC_DISP_HF_AXI_CLK>,
+ <&dispcc DISP_CC_MDSS_MDP_CLK>;
+ clock-names = "iface",
+ "bus",
+ "core";
+
+ resets = <&dispcc DISP_CC_MDSS_CORE_BCR>;
+
+ power-domains = <&dispcc MDSS_GDSC>;
+
+ iommus = <&apps_smmu 0x420 0x2>;
+ interconnects = <&mmrt_virt MASTER_MDP_PORT0 RPM_ALWAYS_TAG
+ &mc_virt SLAVE_EBI_CH0 RPM_ALWAYS_TAG>,
+ <&mem_noc MASTER_AMPSS_M0 RPM_ALWAYS_TAG
+ &config_noc SLAVE_DISPLAY_CFG RPM_ALWAYS_TAG>;
+ interconnect-names = "mdp0-mem",
+ "cpu-cfg";
+
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+
+ status = "disabled";
+
+ mdp: display-controller@5e01000 {
+ compatible = "qcom,shikra-dpu", "qcom,qcm2290-dpu";
+ reg = <0x0 0x05e01000 0x0 0x8f000>,
+ <0x0 0x05eb0000 0x0 0x3000>;
+ reg-names = "mdp",
+ "vbif";
+
+ interrupt-parent = <&mdss>;
+ interrupts = <0>;
+
+ clocks = <&gcc GCC_DISP_HF_AXI_CLK>,
+ <&dispcc DISP_CC_MDSS_AHB_CLK>,
+ <&dispcc DISP_CC_MDSS_MDP_CLK>,
+ <&dispcc DISP_CC_MDSS_MDP_LUT_CLK>,
+ <&dispcc DISP_CC_MDSS_VSYNC_CLK>;
+ clock-names = "bus",
+ "iface",
+ "core",
+ "lut",
+ "vsync";
+
+ operating-points-v2 = <&mdp_opp_table>;
+ power-domains = <&rpmpd QCM2290_VDDCX>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ dpu_intf1_out: endpoint {
+ remote-endpoint = <&mdss_dsi0_in>;
+ };
+ };
+ };
+
+ mdp_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+ opp-19200000 {
+ opp-hz = /bits/ 64 <19200000>;
+ required-opps = <&rpmpd_opp_min_svs>;
+ };
+
+ opp-192000000 {
+ opp-hz = /bits/ 64 <192000000>;
+ required-opps = <&rpmpd_opp_low_svs>;
+ };
+
+ opp-256000000 {
+ opp-hz = /bits/ 64 <256000000>;
+ required-opps = <&rpmpd_opp_svs>;
+ };
+
+ opp-307200000 {
+ opp-hz = /bits/ 64 <307200000>;
+ required-opps = <&rpmpd_opp_svs_plus>;
+ };
+
+ opp-384000000 {
+ opp-hz = /bits/ 64 <384000000>;
+ required-opps = <&rpmpd_opp_nom>;
+ };
+ };
+ };
+
+ mdss_dsi0: dsi@5e94000 {
+ compatible = "qcom,shikra-dsi-ctrl",
+ "qcom,qcm2290-dsi-ctrl",
+ "qcom,mdss-dsi-ctrl";
+ reg = <0x0 0x05e94000 0x0 0x400>;
+ reg-names = "dsi_ctrl";
+
+ interrupt-parent = <&mdss>;
+ interrupts = <4>;
+
+ clocks = <&dispcc DISP_CC_MDSS_BYTE0_CLK>,
+ <&dispcc DISP_CC_MDSS_BYTE0_INTF_CLK>,
+ <&dispcc DISP_CC_MDSS_PCLK0_CLK>,
+ <&dispcc DISP_CC_MDSS_ESC0_CLK>,
+ <&dispcc DISP_CC_MDSS_AHB_CLK>,
+ <&gcc GCC_DISP_HF_AXI_CLK>;
+ clock-names = "byte",
+ "byte_intf",
+ "pixel",
+ "core",
+ "iface",
+ "bus";
+
+ assigned-clocks = <&dispcc DISP_CC_MDSS_BYTE0_CLK_SRC>,
+ <&dispcc DISP_CC_MDSS_PCLK0_CLK_SRC>;
+ assigned-clock-parents = <&mdss_dsi0_phy DSI_BYTE_PLL_CLK>,
+ <&mdss_dsi0_phy DSI_PIXEL_PLL_CLK>;
+
+ operating-points-v2 = <&dsi_opp_table>;
+ power-domains = <&rpmpd QCM2290_VDDCX>;
+ phys = <&mdss_dsi0_phy>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ status = "disabled";
+
+ dsi_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+ opp-19200000 {
+ opp-hz = /bits/ 64 <19200000>;
+ required-opps = <&rpmpd_opp_min_svs>;
+ };
+
+ opp-164000000 {
+ opp-hz = /bits/ 64 <164000000>;
+ required-opps = <&rpmpd_opp_low_svs>;
+ };
+
+ opp-187500000 {
+ opp-hz = /bits/ 64 <187500000>;
+ required-opps = <&rpmpd_opp_svs>;
+ };
+ };
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ mdss_dsi0_in: endpoint {
+ remote-endpoint = <&dpu_intf1_out>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ mdss_dsi0_out: endpoint {
+ };
+ };
+ };
+ };
+
+ mdss_dsi0_phy: phy@5e94400 {
+ compatible = "qcom,dsi-phy-14nm-2290";
+ reg = <0x0 0x05e94400 0x0 0x100>,
+ <0x0 0x05e94500 0x0 0x300>,
+ <0x0 0x05e94800 0x0 0x188>;
+ reg-names = "dsi_phy",
+ "dsi_phy_lane",
+ "dsi_pll";
+
+ clocks = <&dispcc DISP_CC_MDSS_AHB_CLK>,
+ <&rpmcc RPM_SMD_XO_CLK_SRC>;
+ clock-names = "iface",
+ "ref";
+
+ power-domains = <&rpmpd QCM2290_VDDMX>;
+ required-opps = <&rpmpd_opp_nom>;
+
+ #clock-cells = <1>;
+ #phy-cells = <0>;
+
+ status = "disabled";
+ };
+ };
+
dispcc: clock-controller@5f00000 {
compatible = "qcom,shikra-dispcc", "qcom,qcm2290-dispcc";
reg = <0x0 0x05f00000 0x0 0x20000>;
@@ -1997,8 +2196,8 @@ dispcc: clock-controller@5f00000 {
<&rpmcc RPM_SMD_XO_A_CLK_SRC>,
<&gcc GCC_DISP_GPLL0_CLK_SRC>,
<&gcc GCC_DISP_GPLL0_DIV_CLK_SRC>,
- <0>,
- <0>,
+ <&mdss_dsi0_phy DSI_BYTE_PLL_CLK>,
+ <&mdss_dsi0_phy DSI_PIXEL_PLL_CLK>,
<0>,
<0>,
<&sleep_clk>;
--
2.34.1
^ permalink raw reply related
* [PATCH 3/5] arm64: defconfig: Enable ILI7807S DSI panel driver
From: Nabige Aala @ 2026-06-27 10:01 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: linux-arm-msm, devicetree, linux-kernel, arpit.saini, mohit.dsor,
Nabige Aala
In-Reply-To: <20260627-shikra-dt-changes-v1-0-449a402673d0@oss.qualcomm.com>
From: Arpit Saini <arpit.saini@oss.qualcomm.com>
Enable the ILI7807S 1080x1920 video-mode DSI panel driver as a module,
used on the Shikra board.
Signed-off-by: Arpit Saini <arpit.saini@oss.qualcomm.com>
---
arch/arm64/configs/defconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index f2e6ae93e533..0395873ec03f 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -1009,6 +1009,7 @@ CONFIG_DRM_MXSFB=m
CONFIG_DRM_IMX_LCDIF=m
CONFIG_DRM_NOUVEAU=m
CONFIG_DRM_PANEL_BOE_TV101WUM_NL6=m
+CONFIG_DRM_PANEL_ILITEK_ILI7807S=m
CONFIG_DRM_PANEL_LVDS=m
CONFIG_DRM_PANEL_HIMAX_HX8279=m
CONFIG_DRM_PANEL_HIMAX_HX83112A=m
--
2.34.1
^ permalink raw reply related
* [PATCH 2/5] arm64: dts: qcom: shikra-cqm-evk: Enable display and add ili7807s panel
From: Nabige Aala @ 2026-06-27 10:01 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: linux-arm-msm, devicetree, linux-kernel, arpit.saini, mohit.dsor,
Nabige Aala
In-Reply-To: <20260627-shikra-dt-changes-v1-0-449a402673d0@oss.qualcomm.com>
From: Arpit Saini <arpit.saini@oss.qualcomm.com>
Enable the Shikra MDSS display subsystem on the Shikra CQM EVK
board and add the DLC0697 MIPI DSI display panel node.
Pin pm4125_l5 to 1.232V with regulator-allow-set-load
for DSI PHY PLL stability.
Signed-off-by: Arpit Saini <arpit.saini@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts | 104 ++++++++++++++++++++++++++++
1 file changed, 104 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts b/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts
index 683b5245923b..c9ea093cd8ca 100644
--- a/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts
+++ b/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts
@@ -23,6 +23,18 @@ chosen {
stdout-path = "serial0:115200n8";
};
+ lcd_bias: regulator-lcd-bias {
+ compatible = "regulator-fixed";
+ regulator-name = "lcd_bias";
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ vin-supply = <&pm4125_l17>;
+ gpio = <&tlmm 151 GPIO_ACTIVE_HIGH>;
+ enable-active-high;
+ pinctrl-0 = <&lcd_bias_en>;
+ pinctrl-names = "default";
+ };
+
wcn3988-pmu {
compatible = "qcom,wcn3988-pmu";
@@ -60,6 +72,52 @@ vreg_pmu_ch1: ldo4 {
};
};
+&mdss {
+ status = "okay";
+};
+
+&mdss_dsi0 {
+ vdda-supply = <&pm4125_l5>;
+ status = "okay";
+
+ panel@0 {
+ compatible = "dlc,dlc0697", "ilitek,ili7807s";
+ reg = <0>;
+
+ reset-gpios = <&tlmm 3 GPIO_ACTIVE_LOW>;
+
+ vddi-supply = <&pm4125_l15>;
+ avdd-supply = <&lcd_bias>;
+ avee-supply = <&lcd_bias>;
+
+ pinctrl-0 = <&panel_bl_en &panel_rst_n &panel_te_pin>;
+ pinctrl-1 = <&panel_bl_en_suspend &panel_rst_n_suspend>;
+ pinctrl-names = "default", "sleep";
+
+ port {
+ panel_in: endpoint {
+ remote-endpoint = <&mdss_dsi0_out>;
+ };
+ };
+ };
+};
+
+&mdss_dsi0_out {
+ remote-endpoint = <&panel_in>;
+ data-lanes = <0 1 2 3>;
+};
+
+&mdss_dsi0_phy {
+ status = "okay";
+};
+
+&pm4125_l5 {
+ /* DSI VDDA - must be at NOM voltage for PHY PLL lock */
+ regulator-min-microvolt = <1232000>;
+ regulator-max-microvolt = <1232000>;
+ regulator-allow-set-load;
+};
+
&remoteproc_cdsp {
firmware-name = "qcom/shikra/cdsp.mbn";
@@ -116,3 +174,49 @@ &wifi {
status = "okay";
};
+
+&tlmm {
+ lcd_bias_en: lcd-bias-en-state {
+ pins = "gpio151";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-disable;
+ };
+
+ panel_bl_en: panel-bl-en-state {
+ pins = "gpio91";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-disable;
+ output-high;
+ };
+
+ panel_bl_en_suspend: panel-bl-en-suspend-state {
+ pins = "gpio91";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-pull-down;
+ output-low;
+ };
+
+ panel_rst_n: panel-rst-n-state {
+ pins = "gpio3";
+ function = "gpio";
+ drive-strength = <8>;
+ bias-disable;
+ };
+
+ panel_rst_n_suspend: panel-rst-n-suspend-state {
+ pins = "gpio3";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-pull-down;
+ };
+
+ panel_te_pin: panel-te-pin-state {
+ pins = "gpio86";
+ function = "mdp_vsync_p";
+ drive-strength = <2>;
+ bias-pull-down;
+ };
+};
--
2.34.1
^ 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