Devicetree
 help / color / mirror / Atom feed
* Re: [PATCH v2 2/2] arm64: dts: qcom: kaanapali: fix traceNoC probe issue
From: Jie Gan @ 2026-06-25  1:01 UTC (permalink / raw)
  To: Leo Yan, Suzuki K Poulose
  Cc: Konrad Dybcio, Bjorn Andersson, Konrad Dybcio, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Tingwei Zhang, Jingyi Wang,
	Abel Vesa, Mike Leach, James Clark, Yuanfang Zhang, linux-arm-msm,
	devicetree, linux-kernel, coresight, linux-arm-kernel
In-Reply-To: <20260624151610.GC575984@e132581.arm.com>



On 6/24/2026 11:16 PM, Leo Yan wrote:
> On Wed, Jun 24, 2026 at 11:08:32PM +0800, Jie Gan wrote:
> 
> [...]
> 
>>> Why does it fail ? power management ? hw broken ? Is it really AMBA or
>>> do you pretend that to be an AMBA device by faking the CID/PID?
>>
>> The CID reads as 0 from the register, which I suspect is a hardware design
>> issue. I have not yet confirmed this with the hardware team. As a
>> workaround, I provided a fake periphid via a DT property to bypass
>> amba_read_periphid.
>>
>>
>> Leo commented in other thread:
>>>> tnoc.c registers both an AMBA driver and a platform driver. Shouldn't >>it
>>>> be registered as a platform device instead?
>>
>> The platform driver is intended for the interconnect TraceNoC device and is
>> not designed to allocate an ATID. The issue is that the TPDM device borrows
>> the ATID from the TraceNoC device, resulting in the ATID always being 0 when
>> associated with an interconnect NoC device.
>>
>> However, I believe it is acceptable to allocate an ATID for the itNoC device
>> and the issue can be fixed with this way.
> 
> I think so.

Hi Suzuki/Leo

Which solution do you prefer to address the issue?
The interconnect traceNoC platform driver is intended for the itnoc 
device, implying that no TPDM devices are connected to it. So, if I 
modify it to allocate an ATID, I think it would be better to rename the 
“itnoc” node accordingly? Or it's ok to leave it as-is?

BTW, the traceNoC device definitely is an AMBA device with CID/PID 
registers.

Thanks,
Jie


^ permalink raw reply

* Re: [PATCH v2 2/2] clk: ultrarisc: add DP1000 clock driver
From: Jia Wang @ 2026-06-25  1:04 UTC (permalink / raw)
  To: Jia Wang
  Cc: Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-clk, devicetree,
	linux-kernel
In-Reply-To: <20260617-ultrarisc-clock-v2-2-9cb16083e15e@ultrarisc.com>

On 2026-06-17 14:02 +0800, Jia Wang wrote:
> Add a clock driver for the UltraRISC DP1000 SoC.
> 
> The clock tree is driven by a SYSPLL and provides fixed-factor clocks for
> the subsystem and PCIe, divider-based root clocks for GMAC and the UART,
> I2C, and SPI blocks, and per-instance gate clocks for UART0-3, I2C0-3,
> and SPI0-1.
> 
> Signed-off-by: Jia Wang <wangjia@ultrarisc.com>
> ---
>  MAINTAINERS                           |   1 +
>  drivers/clk/Kconfig                   |   1 +
>  drivers/clk/Makefile                  |   1 +
>  drivers/clk/ultrarisc/Kconfig         |  18 ++
>  drivers/clk/ultrarisc/Makefile        |   4 +
>  drivers/clk/ultrarisc/clk-dp1000.c    | 153 ++++++++++++
>  drivers/clk/ultrarisc/clk-ultrarisc.c | 459 ++++++++++++++++++++++++++++++++++
>  drivers/clk/ultrarisc/clk-ultrarisc.h |  73 ++++++
>  8 files changed, 710 insertions(+)
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index b7e43313c65f..aa5021f30cb5 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -27444,6 +27444,7 @@ M:	Jia Wang <wangjia@ultrarisc.com>
>  L:	linux-clk@vger.kernel.org
>  S:	Maintained
>  F:	Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml
> +F:	drivers/clk/ultrarisc/*
>  F:	include/dt-bindings/clock/ultrarisc,dp1000-clk.h
>  
>  ULTRATRONIK BOARD SUPPORT
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index b2efbe9f6acb..75e336858420 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -535,6 +535,7 @@ source "drivers/clk/tenstorrent/Kconfig"
>  source "drivers/clk/thead/Kconfig"
>  source "drivers/clk/stm32/Kconfig"
>  source "drivers/clk/ti/Kconfig"
> +source "drivers/clk/ultrarisc/Kconfig"
>  source "drivers/clk/uniphier/Kconfig"
>  source "drivers/clk/visconti/Kconfig"
>  source "drivers/clk/x86/Kconfig"
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index a3e2862ebd7e..891272e8e1da 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -158,6 +158,7 @@ obj-$(CONFIG_ARCH_TEGRA)		+= tegra/
>  obj-y					+= tenstorrent/
>  obj-$(CONFIG_ARCH_THEAD)		+= thead/
>  obj-y					+= ti/
> +obj-y					+= ultrarisc/
>  obj-$(CONFIG_CLK_UNIPHIER)		+= uniphier/
>  obj-$(CONFIG_ARCH_U8500)		+= ux500/
>  obj-y					+= versatile/
> diff --git a/drivers/clk/ultrarisc/Kconfig b/drivers/clk/ultrarisc/Kconfig
> new file mode 100644
> index 000000000000..2eecc6ac3119
> --- /dev/null
> +++ b/drivers/clk/ultrarisc/Kconfig
> @@ -0,0 +1,18 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config CLK_ULTRARISC
> +	tristate
> +	depends on OF
> +	depends on ARCH_ULTRARISC || COMPILE_TEST
> +
> +config CLK_ULTRARISC_DP1000
> +	tristate "UltraRISC DP1000 clock controller"
> +	select CLK_ULTRARISC
> +	depends on OF && HAS_IOMEM
> +	depends on ARCH_ULTRARISC || COMPILE_TEST
> +	default ARCH_ULTRARISC
> +	help
> +	  This driver provides the clock controller for the UltraRISC
> +	  DP1000 SoC. It exposes the PLL output, derived fixed-factor
> +	  clocks, programmable divider clocks, and peripheral gate
> +	  clocks to Linux consumers.
> diff --git a/drivers/clk/ultrarisc/Makefile b/drivers/clk/ultrarisc/Makefile
> new file mode 100644
> index 000000000000..b013708c9444
> --- /dev/null
> +++ b/drivers/clk/ultrarisc/Makefile
> @@ -0,0 +1,4 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +obj-$(CONFIG_CLK_ULTRARISC) += clk-ultrarisc.o
> +obj-$(CONFIG_CLK_ULTRARISC_DP1000) += clk-dp1000.o
> diff --git a/drivers/clk/ultrarisc/clk-dp1000.c b/drivers/clk/ultrarisc/clk-dp1000.c
> new file mode 100644
> index 000000000000..33a16df8f189
> --- /dev/null
> +++ b/drivers/clk/ultrarisc/clk-dp1000.c
> @@ -0,0 +1,153 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2026 UltraRISC Technology (Shanghai) Co., Ltd.
> + */
> +
> +#include <linux/module.h>
> +
> +#include <dt-bindings/clock/ultrarisc,dp1000-clk.h>
> +
> +#include "clk-ultrarisc.h"
> +
> +#define DP1000_PLL_CFG1_OFFSET		0x400
> +#define DP1000_PLL_CFG2_OFFSET		0x404
> +
> +#define DP1000_CCR_UART_OFFSET		0x220
> +#define DP1000_CCR_I2C_OFFSET		0x224
> +#define DP1000_CCR_GMAC_OFFSET		0x228
> +#define DP1000_CCR_SPI_OFFSET		0x22c
> +#define DP1000_PERI_CLKENA_OFFSET	0x270
> +
> +#define DP1000_CCR_LOAD			BIT(16)
> +
> +#define DP1000_PERI_MAX_RATE		62500000UL
> +#define DP1000_CLK_NUM			21
> +
> +static const struct ultrarisc_pll_layout dp1000_pll_layout = {
> +	.cfg1_offset = DP1000_PLL_CFG1_OFFSET,
> +	.cfg2_offset = DP1000_PLL_CFG2_OFFSET,
> +	.frac_mask = GENMASK(23, 0),
> +	.m_mask = GENMASK(23, 16),
> +	.n_mask = GENMASK(11, 6),
> +	.oddiv1_mask = GENMASK(4, 3),
> +	.oddiv2_mask = GENMASK(1, 0),
> +};
> +
> +static const struct ultrarisc_pll_desc dp1000_plls[] = {
> +	{
> +		.id = DP1000_CLK_SYSPLL,
> +		.name = "syspll_clk",
> +	},
> +};
> +
> +#define DP1000_FIXED_FACTOR(_id, _name, _parent, _mult, _div)	\
> +	{							\
> +		.id = (_id),					\
> +		.name = (_name),				\
> +		.parent_id = (_parent),				\
> +		.mult = (_mult),				\
> +		.div = (_div),					\
> +	}
> +
> +#define DP1000_DIV(_id, _name, _offset, _parent, _max_rate)	\
> +	{							\
> +		.id = (_id),					\
> +		.name = (_name),				\
> +		.offset = (_offset),				\
> +		.parent_id = (_parent),				\
> +		.max_rate = (_max_rate),			\
> +		.load_mask = DP1000_CCR_LOAD,			\
> +		.div_shift = 8,					\
> +		.div_width = 4,					\
> +		.gate_bit = 0,					\
> +		.divider_flags = CLK_DIVIDER_ONE_BASED,		\
> +		.gate_flags = 0,				\
> +	}
> +
> +#define DP1000_GATE(_id, _name, _parent, _bit)		\
> +	{							\
> +		.id = (_id),					\
> +		.name = (_name),				\
> +		.offset = DP1000_PERI_CLKENA_OFFSET,		\
> +		.parent_id = (_parent),				\
> +		.gate_bit = (_bit),				\
> +		.gate_flags = 0,				\
> +	}
> +
> +static const struct ultrarisc_fixed_factor_desc dp1000_fixed_factor_clks[] = {
> +	DP1000_FIXED_FACTOR(DP1000_CLK_SYSPLL_DIV2, "syspll_div2_clk",
> +			    DP1000_CLK_SYSPLL, 1, 2),
> +	DP1000_FIXED_FACTOR(DP1000_CLK_SUBSYS, "subsys_clk",
> +			    DP1000_CLK_SYSPLL_DIV2, 1, 2),
> +	DP1000_FIXED_FACTOR(DP1000_CLK_PCIE_DBI, "pcie_dbi_clk",
> +			    DP1000_CLK_SYSPLL, 1, 10),
> +	DP1000_FIXED_FACTOR(DP1000_CLK_PCIEX4_CORE, "pciex4_core_clk",
> +			    DP1000_CLK_SYSPLL, 1, 2),
> +	DP1000_FIXED_FACTOR(DP1000_CLK_PCIEX16_CORE, "pciex16_core_clk",
> +			    DP1000_CLK_SYSPLL, 1, 1),
> +	DP1000_FIXED_FACTOR(DP1000_CLK_PCIE_AUX, "pcie_aux_clk",
> +			    DP1000_CLK_SYSPLL, 1, 40),
> +};
> +
> +static const struct ultrarisc_divider_desc dp1000_divider_clks[] = {
> +	DP1000_DIV(DP1000_CLK_GMAC, "gmac_clk", DP1000_CCR_GMAC_OFFSET,
> +		   DP1000_CLK_SYSPLL_DIV2, 0),
> +	DP1000_DIV(DP1000_CLK_UART_ROOT, "uart_root_clk",
> +		   DP1000_CCR_UART_OFFSET, DP1000_CLK_SUBSYS,
> +		   DP1000_PERI_MAX_RATE),
> +	DP1000_DIV(DP1000_CLK_I2C_ROOT, "i2c_root_clk",
> +		   DP1000_CCR_I2C_OFFSET, DP1000_CLK_SUBSYS,
> +		   DP1000_PERI_MAX_RATE),
> +	DP1000_DIV(DP1000_CLK_SPI_ROOT, "spi_root_clk",
> +		   DP1000_CCR_SPI_OFFSET, DP1000_CLK_SUBSYS,
> +		   DP1000_PERI_MAX_RATE),
> +};
> +
> +static const struct ultrarisc_gate_desc dp1000_gate_clks[] = {
> +	DP1000_GATE(DP1000_CLK_UART0, "uart0_clk", DP1000_CLK_UART_ROOT, 0),
> +	DP1000_GATE(DP1000_CLK_UART1, "uart1_clk", DP1000_CLK_UART_ROOT, 1),
> +	DP1000_GATE(DP1000_CLK_UART2, "uart2_clk", DP1000_CLK_UART_ROOT, 2),
> +	DP1000_GATE(DP1000_CLK_UART3, "uart3_clk", DP1000_CLK_UART_ROOT, 3),
> +	DP1000_GATE(DP1000_CLK_I2C0, "i2c0_clk", DP1000_CLK_I2C_ROOT, 4),
> +	DP1000_GATE(DP1000_CLK_I2C1, "i2c1_clk", DP1000_CLK_I2C_ROOT, 5),
> +	DP1000_GATE(DP1000_CLK_I2C2, "i2c2_clk", DP1000_CLK_I2C_ROOT, 6),
> +	DP1000_GATE(DP1000_CLK_I2C3, "i2c3_clk", DP1000_CLK_I2C_ROOT, 7),
> +	DP1000_GATE(DP1000_CLK_SPI0, "spi0_clk", DP1000_CLK_SPI_ROOT, 8),
> +	DP1000_GATE(DP1000_CLK_SPI1, "spi1_clk", DP1000_CLK_SPI_ROOT, 9),
> +};
> +
> +static const struct ultrarisc_clk_soc_data dp1000_clk_soc_data = {
> +	.num_clks = DP1000_CLK_NUM,
> +	.pll_layout = &dp1000_pll_layout,
> +	.plls = dp1000_plls,
> +	.num_plls = ARRAY_SIZE(dp1000_plls),
> +	.fixed_factors = dp1000_fixed_factor_clks,
> +	.num_fixed_factors = ARRAY_SIZE(dp1000_fixed_factor_clks),
> +	.dividers = dp1000_divider_clks,
> +	.num_dividers = ARRAY_SIZE(dp1000_divider_clks),
> +	.gates = dp1000_gate_clks,
> +	.num_gates = ARRAY_SIZE(dp1000_gate_clks),
> +};
> +
> +static int dp1000_clk_probe(struct platform_device *pdev)
> +{
> +	return ultrarisc_clk_probe(pdev, &dp1000_clk_soc_data);
> +}
> +
> +static const struct of_device_id dp1000_clk_of_match[] = {
> +	{ .compatible = "ultrarisc,dp1000-clk" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, dp1000_clk_of_match);
> +
> +static struct platform_driver dp1000_clk_driver = {
> +	.probe = dp1000_clk_probe,
> +	.driver = {
> +		.name = "ultrarisc-dp1000-clk",
> +		.of_match_table = dp1000_clk_of_match,
> +	},
> +};
> +module_platform_driver(dp1000_clk_driver);
> +
> +MODULE_DESCRIPTION("UltraRISC DP1000 clock controller");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/ultrarisc/clk-ultrarisc.c b/drivers/clk/ultrarisc/clk-ultrarisc.c
> new file mode 100644
> index 000000000000..4ef222348dd7
> --- /dev/null
> +++ b/drivers/clk/ultrarisc/clk-ultrarisc.c
> @@ -0,0 +1,459 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2026 UltraRISC Technology (Shanghai) Co., Ltd.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/clk-provider.h>
> +#include <linux/io.h>
> +#include <linux/math64.h>
> +#include <linux/module.h>
> +#include <linux/of_clk.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +
> +#include "clk-ultrarisc.h"
> +
> +struct ultrarisc_pll_clk {
> +	struct clk_hw hw;
> +	void __iomem *base;
> +	const struct ultrarisc_pll_layout *layout;
> +};
> +
> +struct ultrarisc_divider_clk {
> +	struct clk_divider divider;
> +	struct clk_gate gate;
> +	u32 load_mask;
> +};
> +
> +#define to_ultrarisc_pll_clk(_hw) \
> +	container_of(_hw, struct ultrarisc_pll_clk, hw)
> +
> +static inline struct ultrarisc_divider_clk *
> +to_ultrarisc_divider_clk(struct clk_hw *hw)
> +{
> +	struct clk_divider *divider = to_clk_divider(hw);
> +
> +	return container_of(divider, struct ultrarisc_divider_clk, divider);
> +}
> +
> +static unsigned long ultrarisc_pll_recalc_rate(struct clk_hw *hw,
> +					       unsigned long parent_rate)
> +{
> +	struct ultrarisc_pll_clk *pll = to_ultrarisc_pll_clk(hw);
> +	const struct ultrarisc_pll_layout *layout = pll->layout;
> +	u32 oddiv1_div, oddiv2_div;
> +	u64 mult, rate, den;
> +	u32 frac, m, n;
> +	u32 cfg1, cfg2;
> +
> +	cfg1 = readl_relaxed(pll->base + layout->cfg1_offset);
> +	cfg2 = readl_relaxed(pll->base + layout->cfg2_offset);
> +
> +	frac = field_get(layout->frac_mask, cfg1);
> +	m = field_get(layout->m_mask, cfg2);
> +	n = field_get(layout->n_mask, cfg2);
> +	oddiv1_div = 1U << field_get(layout->oddiv1_mask, cfg2);
> +	oddiv2_div = 1U << field_get(layout->oddiv2_mask, cfg2);
> +
> +	if (!n)
> +		return 0;
> +
> +	/*
> +	 * The output frequency is calculated as:
> +	 * fvco = parent * (m + frac / 2^24) / n
> +	 * fout = fvco / (2^oddiv1_raw * 2^oddiv2_raw)
> +	 *
> +	 * The output divider values are derived from the raw register field
> +	 * values as:
> +	 * oddivX_div = 1 << oddivX_raw
> +	 */
> +	mult = ((u64)m << 24) + frac;
> +	rate = (u64)parent_rate * mult;
> +	den = ((u64)n << 24) * oddiv1_div * oddiv2_div;
> +
> +	return div64_u64(rate + (den >> 1), den);
> +}
> +
> +static const struct clk_ops ultrarisc_pll_ro_ops = {
> +	.recalc_rate = ultrarisc_pll_recalc_rate,
> +};
> +
> +static unsigned long
> +ultrarisc_divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
> +{
> +	struct clk_divider *divider = to_clk_divider(hw);
> +	u32 val;
> +
> +	val = readl_relaxed(divider->reg) >> divider->shift;
> +	val &= clk_div_mask(divider->width);
> +
> +	return divider_recalc_rate(hw, parent_rate, val, divider->table,
> +				   divider->flags, divider->width);
> +}
> +
> +static int ultrarisc_divider_determine_rate(struct clk_hw *hw,
> +					    struct clk_rate_request *req)
> +{
> +	struct clk_divider *divider = to_clk_divider(hw);
> +
> +	return divider_determine_rate(hw, req, divider->table, divider->width,
> +				      divider->flags);
> +}
> +
> +static int ultrarisc_divider_set_rate(struct clk_hw *hw, unsigned long rate,
> +				      unsigned long parent_rate)
> +{
> +	struct ultrarisc_divider_clk *divider_clk = to_ultrarisc_divider_clk(hw);
> +	struct clk_divider *divider = &divider_clk->divider;
> +	int value;
> +	u32 val;
> +
> +	value = divider_get_val(rate, parent_rate, divider->table,
> +				divider->width, divider->flags);
> +	if (value < 0)
> +		return value;
> +
> +	scoped_guard(spinlock_irqsave, divider->lock) {
> +		val = readl_relaxed(divider->reg);
> +		val &= ~(clk_div_mask(divider->width) << divider->shift);
> +		val |= value << divider->shift;
> +		writel_relaxed(val, divider->reg);
> +
> +		if (divider_clk->load_mask) {
> +			/*
> +			 * Program the new divider field, then write 1 to the
> +			 * load bit to trigger the update. The load bit is
> +			 * write-triggered and reads back as 0 on this hardware.
> +			 */
> +			writel_relaxed(val | divider_clk->load_mask,
> +				       divider->reg);
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops ultrarisc_divider_ops = {
> +	.recalc_rate = ultrarisc_divider_recalc_rate,
> +	.determine_rate = ultrarisc_divider_determine_rate,
> +	.set_rate = ultrarisc_divider_set_rate,
> +};
> +
> +static void ultrarisc_clk_unregister_composite(void *data)
> +{
> +	clk_hw_unregister_composite(data);
> +}
> +
> +static struct clk_hw *
> +ultrarisc_clk_hw_register_composite(struct device *dev, const char *name,
> +				    const char * const *parent_names,
> +				    int num_parents, struct clk_hw *mux_hw,
> +				    const struct clk_ops *mux_ops,
> +				    struct clk_hw *rate_hw,
> +				    const struct clk_ops *rate_ops,
> +				    struct clk_hw *gate_hw,
> +				    const struct clk_ops *gate_ops,
> +				    unsigned long flags)
> +{
> +	struct clk_hw *hw;
> +	int ret;
> +
> +	hw = clk_hw_register_composite(dev, name, parent_names, num_parents,
> +				       mux_hw, mux_ops, rate_hw, rate_ops,
> +				       gate_hw, gate_ops, flags);
> +	if (IS_ERR(hw))
> +		return hw;
> +
> +	ret = devm_add_action_or_reset(dev, ultrarisc_clk_unregister_composite,
> +				       hw);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	return hw;
> +}
> +
> +static struct clk_hw *ultrarisc_clk_register_pll(struct device *dev,
> +						 const struct ultrarisc_pll_desc *desc,
> +						 const struct ultrarisc_pll_layout *layout,
> +						 void __iomem *base)
> +{
> +	struct clk_parent_data pdata = { .index = 0 };
> +	struct ultrarisc_pll_clk *pll;
> +	struct clk_init_data init = {
> +		.name = desc->name,
> +		.ops = &ultrarisc_pll_ro_ops,
> +		.parent_data = &pdata,
> +		.num_parents = 1,
> +		.flags = CLK_GET_RATE_NOCACHE,
> +	};
> +	int ret;
> +
> +	pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
> +	if (!pll)
> +		return ERR_PTR(-ENOMEM);
> +
> +	pll->base = base;
> +	pll->layout = layout;
> +	pll->hw.init = &init;
> +
> +	ret = devm_clk_hw_register(dev, &pll->hw);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	return &pll->hw;
> +}
> +
> +static struct clk_hw *
> +ultrarisc_clk_register_divider(struct device *dev,
> +			       const struct ultrarisc_divider_desc *desc,
> +			       struct clk_hw *parent_hw, void __iomem *base,
> +			       spinlock_t *lock)
> +{
> +	const char * const parent_names[] = { clk_hw_get_name(parent_hw) };
> +	void __iomem *reg = base + desc->offset;
> +	struct ultrarisc_divider_clk *divider;
> +	const struct clk_ops *gate_ops = NULL;
> +	struct clk_hw *gate_hw = NULL;
> +
> +	if (!desc->div_width)
> +		return ERR_PTR(-EINVAL);
> +
> +	if (!lock)
> +		return ERR_PTR(-EINVAL);
> +
> +	divider = devm_kzalloc(dev, sizeof(*divider), GFP_KERNEL);
> +	if (!divider)
> +		return ERR_PTR(-ENOMEM);
> +
> +	divider->divider.reg = reg;
> +	divider->divider.shift = desc->div_shift;
> +	divider->divider.width = desc->div_width;
> +	divider->divider.flags = desc->divider_flags;
> +	divider->divider.lock = lock;
> +	divider->load_mask = desc->load_mask;
> +
> +	if (desc->gate_bit != ULTRARISC_CLK_NO_GATE) {
> +		divider->gate.reg = reg;
> +		divider->gate.bit_idx = desc->gate_bit;
> +		divider->gate.flags = desc->gate_flags;
> +		divider->gate.lock = lock;
> +		gate_hw = &divider->gate.hw;
> +		gate_ops = &clk_gate_ops;
> +	}
> +
> +	return ultrarisc_clk_hw_register_composite(dev, desc->name,
> +						   parent_names, 1,
> +						   NULL, NULL,
> +						   &divider->divider.hw,
> +						   &ultrarisc_divider_ops,
> +						   gate_hw, gate_ops,
> +						   CLK_GET_RATE_NOCACHE);
> +}
> +
> +static int ultrarisc_clk_register_fixed_factors(struct device *dev,
> +						struct clk_hw_onecell_data *clk_data,
> +						const struct ultrarisc_clk_soc_data *soc_data)
> +{
> +	u32 i;
> +
> +	for (i = 0; i < soc_data->num_fixed_factors; i++) {
> +		const struct ultrarisc_fixed_factor_desc *desc;
> +		struct clk_hw *parent_hw;
> +		struct clk_hw *hw;
> +
> +		desc = &soc_data->fixed_factors[i];
> +		if (desc->id >= clk_data->num ||
> +		    desc->parent_id >= clk_data->num)
> +			return -EINVAL;
> +
> +		parent_hw = clk_data->hws[desc->parent_id];
> +		if (!parent_hw)
> +			return -EINVAL;
> +
> +		hw = devm_clk_hw_register_fixed_factor_parent_hw(dev, desc->name,
> +								 parent_hw,
> +								 CLK_GET_RATE_NOCACHE,
> +								 desc->mult,
> +								 desc->div);
> +		if (IS_ERR(hw))
> +			return PTR_ERR(hw);
> +
> +		clk_data->hws[desc->id] = hw;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ultrarisc_clk_register_plls(struct platform_device *pdev,
> +				       struct clk_hw_onecell_data *clk_data,
> +				       const struct ultrarisc_clk_soc_data *soc_data,
> +				       void __iomem *base)
> +{
> +	struct device *dev = &pdev->dev;
> +	u32 i;
> +
> +	for (i = 0; i < soc_data->num_plls; i++) {
> +		const struct ultrarisc_pll_desc *desc = &soc_data->plls[i];
> +		struct clk_hw *hw;
> +
> +		if (desc->id >= clk_data->num)
> +			return -EINVAL;
> +
> +		hw = ultrarisc_clk_register_pll(dev, desc, soc_data->pll_layout,
> +						base);
> +		if (IS_ERR(hw))
> +			return PTR_ERR(hw);
> +
> +		clk_data->hws[desc->id] = hw;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ultrarisc_clk_register_dividers(struct platform_device *pdev,
> +					   struct clk_hw_onecell_data *clk_data,
> +					   const struct ultrarisc_clk_soc_data *soc_data,
> +					   void __iomem *base,
> +					   spinlock_t *lock)
> +{
> +	struct device *dev = &pdev->dev;
> +	u32 i;
> +
> +	for (i = 0; i < soc_data->num_dividers; i++) {
> +		const struct ultrarisc_divider_desc *desc;
> +		struct clk_hw *parent_hw;
> +		struct clk_hw *hw;
> +
> +		desc = &soc_data->dividers[i];
> +		if (desc->id >= clk_data->num ||
> +		    desc->parent_id >= clk_data->num)
> +			return -EINVAL;
> +
> +		parent_hw = clk_data->hws[desc->parent_id];
> +		if (!parent_hw)
> +			return -EINVAL;
> +
> +		hw = ultrarisc_clk_register_divider(dev, desc, parent_hw, base,
> +						    lock);
> +		if (IS_ERR(hw))
> +			return PTR_ERR(hw);
> +
> +		if (desc->max_rate) {
> +			unsigned long rate;
> +
> +			clk_hw_set_rate_range(hw, 0, desc->max_rate);
> +
> +			rate = clk_hw_get_rate(hw);
> +			if (rate > desc->max_rate)
> +				dev_warn(dev, "%s rate %lu exceeds max %lu\n",
> +					 desc->name, rate, desc->max_rate);
> +		}
> +
> +		clk_data->hws[desc->id] = hw;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ultrarisc_clk_register_gates(struct platform_device *pdev,
> +					struct clk_hw_onecell_data *clk_data,
> +					const struct ultrarisc_clk_soc_data *soc_data,
> +					void __iomem *base,
> +					spinlock_t *lock)
> +{
> +	struct device *dev = &pdev->dev;
> +	u32 i;
> +
> +	for (i = 0; i < soc_data->num_gates; i++) {
> +		const struct ultrarisc_gate_desc *desc;
> +		struct clk_hw *parent_hw;
> +		struct clk_hw *hw;
> +
> +		desc = &soc_data->gates[i];
> +		if (desc->id >= clk_data->num ||
> +		    desc->parent_id >= clk_data->num)
> +			return -EINVAL;
> +
> +		parent_hw = clk_data->hws[desc->parent_id];
> +		if (!parent_hw)
> +			return -EINVAL;
> +
> +		hw = devm_clk_hw_register_gate_parent_hw(dev, desc->name,
> +							 parent_hw,
> +							 CLK_GET_RATE_NOCACHE,
> +							 base + desc->offset,
> +							 desc->gate_bit,
> +							 desc->gate_flags,
> +							 lock);
> +		if (IS_ERR(hw))
> +			return PTR_ERR(hw);
> +
> +		clk_data->hws[desc->id] = hw;
> +	}
> +
> +	return 0;
> +}
> +
> +int ultrarisc_clk_probe(struct platform_device *pdev,
> +			const struct ultrarisc_clk_soc_data *soc_data)
> +{
> +	struct clk_hw_onecell_data *clk_data;
> +	struct device *dev = &pdev->dev;
> +	void __iomem *base;
> +	spinlock_t *lock;
> +	int ret;
> +
> +	if (!soc_data)
> +		return -EINVAL;
> +
> +	lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL);
> +	if (!lock)
> +		return -ENOMEM;
> +
> +	spin_lock_init(lock);
> +
> +	clk_data = devm_kzalloc(dev, struct_size(clk_data, hws,
> +						 soc_data->num_clks),
> +				GFP_KERNEL);
> +	if (!clk_data)
> +		return -ENOMEM;
> +
> +	clk_data->num = soc_data->num_clks;
> +
> +	base = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(base))
> +		return PTR_ERR(base);
> +
> +	ret = ultrarisc_clk_register_plls(pdev, clk_data, soc_data, base);
> +	if (ret)
> +		return ret;
> +
> +	ret = ultrarisc_clk_register_fixed_factors(dev, clk_data, soc_data);
> +	if (ret)
> +		return ret;
> +
> +	ret = ultrarisc_clk_register_dividers(pdev, clk_data, soc_data, base,
> +					      lock);
> +	if (ret)
> +		return ret;
> +
> +	ret = ultrarisc_clk_register_gates(pdev, clk_data, soc_data, base,
> +					   lock);
> +	if (ret)
> +		return ret;
> +
> +	for (int i = 0; i < clk_data->num; i++) {
> +		if (!clk_data->hws[i]) {
> +			dev_err(dev, "missing clock ID %u\n", i);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get,
> +					   clk_data);
> +}
> +EXPORT_SYMBOL_GPL(ultrarisc_clk_probe);
> +
> +MODULE_DESCRIPTION("UltraRISC clock core driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/ultrarisc/clk-ultrarisc.h b/drivers/clk/ultrarisc/clk-ultrarisc.h
> new file mode 100644
> index 000000000000..1281196bb414
> --- /dev/null
> +++ b/drivers/clk/ultrarisc/clk-ultrarisc.h
> @@ -0,0 +1,73 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +#ifndef __ULTRARISC_CLK_ULTRARISC_H
> +#define __ULTRARISC_CLK_ULTRARISC_H
> +
> +#include <linux/clk-provider.h>
> +#include <linux/platform_device.h>
> +#include <linux/types.h>
> +
> +#define ULTRARISC_CLK_NO_GATE		(-1)
> +
> +struct ultrarisc_pll_layout {
> +	u32 cfg1_offset;
> +	u32 cfg2_offset;
> +	u32 frac_mask;
> +	u32 m_mask;
> +	u32 n_mask;
> +	u32 oddiv1_mask;
> +	u32 oddiv2_mask;
> +};
> +
> +struct ultrarisc_pll_desc {
> +	u32 id;
> +	const char *name;
> +};
> +
> +struct ultrarisc_fixed_factor_desc {
> +	u32 id;
> +	const char *name;
> +	u32 parent_id;
> +	u32 mult;
> +	u32 div;
> +};
> +
> +struct ultrarisc_divider_desc {
> +	u32 id;
> +	const char *name;
> +	u32 offset;
> +	u32 parent_id;
> +	unsigned long max_rate;
> +	u32 load_mask;
> +	u8 div_shift;
> +	u8 div_width;
> +	s8 gate_bit;
> +	u16 divider_flags;
> +	u8 gate_flags;
> +};
> +
> +struct ultrarisc_gate_desc {
> +	u32 id;
> +	const char *name;
> +	u32 offset;
> +	u32 parent_id;
> +	u8 gate_bit;
> +	u8 gate_flags;
> +};
> +
> +struct ultrarisc_clk_soc_data {
> +	const struct ultrarisc_pll_layout *pll_layout;
> +	const struct ultrarisc_pll_desc *plls;
> +	u32 num_plls;
> +	const struct ultrarisc_fixed_factor_desc *fixed_factors;
> +	u32 num_fixed_factors;
> +	const struct ultrarisc_divider_desc *dividers;
> +	u32 num_dividers;
> +	const struct ultrarisc_gate_desc *gates;
> +	u32 num_gates;
> +	u32 num_clks;
> +};
> +
> +int ultrarisc_clk_probe(struct platform_device *pdev,
> +			const struct ultrarisc_clk_soc_data *soc_data);
> +
> +#endif /* __ULTRARISC_CLK_ULTRARISC_H */
> 
> -- 
> 2.34.1
> 
>

Gentle ping.

Best Regards,
Jia Wang 



^ permalink raw reply

* Re: [PATCH v8] arm64: dts: qcom: kodiak: Add EL2 overlay
From: Miaoqing Pan @ 2026-06-25  1:14 UTC (permalink / raw)
  To: Mukesh Ojha, Bjorn Andersson, Konrad Dybcio, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: linux-arm-msm, devicetree, linux-kernel, Sumit Garg
In-Reply-To: <20260624063952.2242702-1-mukesh.ojha@oss.qualcomm.com>



On 6/24/2026 2:39 PM, Mukesh Ojha wrote:
> All the existing variants Kodiak boards are using Gunyah hypervisor
> which means that, so far, Linux-based OS could only boot in EL1 on those
> devices.  However, it is possible for us to boot Linux at EL2 on these
> devices [1].
>
> When running under Gunyah, the remote processor firmware IOMMU
> streams are controlled by Gunyah. However, without Gunyah, the IOMMU is
> managed by the consumer of this DeviceTree. Therefore, describe the
> firmware streams for each remote processor.
>
> Add a EL2-specific DT overlay and apply it to Kodiak IOT variant
> devices to create -el2.dtb for each of them alongside "normal" dtb.
>
> Note that modem and media subsystems haven't been supported yet due
> to missing dependencies. For GPU to work, zap shader is disabled and
> in EL2 mode the kernel owns hardware watchdog which is enabled here.
> And for wifi to work wpss copy engine memory need to be mapped for
> WPSS firmware to work which is aligning with sc7280 chrome.
>
> [1]
> https://docs.qualcomm.com/bundle/publicresource/topics/80-70020-4/boot-developer-touchpoints.html#uefi
>
> Co-developed-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
> Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
> Signed-off-by: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com>
> ---
> Changes in v8: https://lore.kernel.org/lkml/20260522115936.201208-2-sumit.garg@kernel.org/
>   - Added a wpss copy engine memory similar to chrome for Wifi to work.
>   - WPSS does not have firmware Stream, so that was removed.
>   - Added wifi streams similar to chrome for wifi to work.
>   - Removed this patch from Generic Pas patch series, can be followed
>     separately.
>   - Moved Sumit as co-author as part of modification done to the patch
>     in the past.
>   - Added some more kodiak's board variants in the makefile.
>
> Changes in v1-v7:
>   - mpss was disabled and will be enabled once the dependencies patches
>    get merged.
>
>   arch/arm64/boot/dts/qcom/Makefile        | 12 ++++++
>   arch/arm64/boot/dts/qcom/kodiak-el2.dtso | 52 ++++++++++++++++++++++++
>   arch/arm64/boot/dts/qcom/kodiak.dtsi     |  2 +-
>   3 files changed, 65 insertions(+), 1 deletion(-)
>   create mode 100644 arch/arm64/boot/dts/qcom/kodiak-el2.dtso
>
> diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile
> index 6f33c4e2f09c..d2cee1190954 100644
> --- a/arch/arm64/boot/dts/qcom/Makefile
> +++ b/arch/arm64/boot/dts/qcom/Makefile
> @@ -164,7 +164,11 @@ purwa-iot-evk-el2-dtbs	:= purwa-iot-evk.dtb x1-el2.dtbo
>   
>   dtb-$(CONFIG_ARCH_QCOM)	+= purwa-iot-evk-el2.dtb
>   dtb-$(CONFIG_ARCH_QCOM)	+= qcm6490-fairphone-fp5.dtb
> +
>   dtb-$(CONFIG_ARCH_QCOM)	+= qcm6490-idp.dtb
> +qcm6490-idp-el2-dtbs := qcm6490-idp.dtb kodiak-el2.dtbo
> +dtb-$(CONFIG_ARCH_QCOM)	+= qcm6490-idp-el2.dtb
> +
>   dtb-$(CONFIG_ARCH_QCOM)	+= qcm6490-particle-tachyon.dtb
>   dtb-$(CONFIG_ARCH_QCOM)	+= qcm6490-shift-otter.dtb
>   dtb-$(CONFIG_ARCH_QCOM)	+= qcs404-evb-1000.dtb
> @@ -176,12 +180,20 @@ qcs615-ride-el2-dtbs := qcs615-ride.dtb talos-el2.dtbo
>   dtb-$(CONFIG_ARCH_QCOM)	+= qcs615-ride-el2.dtb
>   dtb-$(CONFIG_ARCH_QCOM)	+= qcs6490-radxa-dragon-q6a.dtb
>   dtb-$(CONFIG_ARCH_QCOM)	+= qcs6490-rb3gen2.dtb
> +qcs6490-rb3gen2-el2-dtbs := qcs6490-rb3gen2.dtb kodiak-el2.dtbo
> +dtb-$(CONFIG_ARCH_QCOM)	+= qcs6490-rb3gen2-el2.dtb
>   
>   qcs6490-rb3gen2-vision-mezzanine-dtbs := qcs6490-rb3gen2.dtb qcs6490-rb3gen2-vision-mezzanine.dtbo
>   qcs6490-rb3gen2-industrial-mezzanine-dtbs := qcs6490-rb3gen2.dtb qcs6490-rb3gen2-industrial-mezzanine.dtbo
>   
>   dtb-$(CONFIG_ARCH_QCOM)	+= qcs6490-rb3gen2-industrial-mezzanine.dtb
> +qcs6490-rb3gen2-industrial-mezzanine-el2-dtbs := qcs6490-rb3gen2-industrial-mezzanine.dtb kodiak-el2.dtbo
> +dtb-$(CONFIG_ARCH_QCOM)	+= qcs6490-rb3gen2-industrial-mezzanine-el2.dtb
> +
>   dtb-$(CONFIG_ARCH_QCOM)	+= qcs6490-rb3gen2-vision-mezzanine.dtb
> +qcs6490-rb3gen2-vision-mezzanine-el2-dtbs := qcs6490-rb3gen2-vision-mezzanine.dtb kodiak-el2.dtbo
> +dtb-$(CONFIG_ARCH_QCOM)	+= qcs6490-rb3gen2-vision-mezzanine-el2.dtb
> +
>   dtb-$(CONFIG_ARCH_QCOM)	+= qcs6490-thundercomm-minipc-g1iot.dtb
>   dtb-$(CONFIG_ARCH_QCOM)	+= qcs6490-thundercomm-rubikpi3.dtb
>   dtb-$(CONFIG_ARCH_QCOM)	+= qcs8300-ride.dtb
> diff --git a/arch/arm64/boot/dts/qcom/kodiak-el2.dtso b/arch/arm64/boot/dts/qcom/kodiak-el2.dtso
> new file mode 100644
> index 000000000000..91e4cda45b49
> --- /dev/null
> +++ b/arch/arm64/boot/dts/qcom/kodiak-el2.dtso
> @@ -0,0 +1,52 @@
> +// SPDX-License-Identifier: BSD-3-Clause
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + *
> + * Kodiak specific modifications required to boot in EL2.
> + */
> +
> +/dts-v1/;
> +/plugin/;
> +
> +&gpu_zap_shader {
> +	status = "disabled";
> +};
> +
> +&remoteproc_adsp {
> +	iommus = <&apps_smmu 0x1800 0x0>;
> +};
> +
> +&remoteproc_cdsp {
> +	iommus = <&apps_smmu 0x11a0 0x0400>;
> +};
> +
> +&remoteproc_mpss {
> +	status = "disabled";
> +};
> +
> +&reserved_memory {
> +	#address-cells = <2>;
> +	#size-cells = <2>;
> +
> +	wlan_ce_mem: wlan-ce@4cd000 {
> +		no-map;
> +		reg = <0x0 0x004cd000 0x0 0x1000>;
> +	};
> +};
> +
Is it necessary to redefine |wlan_ce_mem|? Can we consider updating 
|qcs6490-rb3gen2.dts|?
I have verified that with the following changes, *NON-KVM works fine*, 
and |wlan_ce_mem| is only used by the WCN6750 firmware.

--- a/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts
+++ b/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dts
@@ -26,7 +26,6 @@
/delete-node/ &adsp_mem;
/delete-node/ &cdsp_mem;
/delete-node/ &video_mem;
-/delete-node/ &wlan_ce_mem;
/delete-node/ &wpss_mem;
/delete-node/ &xbl_mem;

@@ -1686,7 +1685,6 @@ &venus {
};

&wifi {
-       memory-region = <&wlan_fw_mem>;
         qcom,calibration-variant = "Qualcomm_rb3gen2";


> +&venus {
> +	status = "disabled";
> +};
> +
> +&watchdog {
> +	status = "okay";
> +};
> +
> +&wifi {
> +	memory-region = <&wlan_fw_mem>, <&wlan_ce_mem>;
> +	status = "okay";
> +
> +	wifi-firmware {
> +		iommus = <&apps_smmu 0x1c02 0x1>;
> +	};
> +};
> diff --git a/arch/arm64/boot/dts/qcom/kodiak.dtsi b/arch/arm64/boot/dts/qcom/kodiak.dtsi
> index fa540d8c2615..2486d15fa2ba 100644
> --- a/arch/arm64/boot/dts/qcom/kodiak.dtsi
> +++ b/arch/arm64/boot/dts/qcom/kodiak.dtsi
> @@ -91,7 +91,7 @@ sleep_clk: sleep-clk {
>   		};
>   	};
>   
> -	reserved-memory {
> +	reserved_memory: reserved-memory {
>   		#address-cells = <2>;
>   		#size-cells = <2>;
>   		ranges;


^ permalink raw reply

* Re: [PATCH v8 2/5] thermal: samsung: Add Exynos ACPM TMU driver GS101
From: Alexey Klimov @ 2026-06-25  1:32 UTC (permalink / raw)
  To: Tudor Ambarus, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
	Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Bartlomiej Zolnierkiewicz, Krzysztof Kozlowski, Kees Cook,
	Gustavo A. R. Silva, Peter Griffin, André Draszik,
	Alim Akhtar
  Cc: jyescas, linux-kernel, linux-samsung-soc, linux-pm, devicetree,
	linux-hardening, linux-arm-kernel, Krzysztof Kozlowski
In-Reply-To: <20260603-acpm-tmu-v8-2-0f1810a356e6@linaro.org>

On Wed Jun 3, 2026 at 2:00 PM BST, Tudor Ambarus wrote:
> Add driver for the Thermal Management Unit (TMU) managed via the Alive
> Clock and Power Manager (ACPM), found on Samsung Exynos SoCs such as
> the Google GS101.
>
> The TMU on the GS101 utilizes a hybrid management model shared between
> the Application Processor (AP) and the ACPM firmware. The driver
> maintains direct memory-mapped access to the TMU interrupt pending
> registers to identify thermal events, while delegating functional
> tasks - such as sensor initialization, threshold configuration, and
> temperature acquisition, to the ACPM firmware via the ACPM IPC
> protocol.

[..]

> +++ b/drivers/thermal/samsung/acpm-tmu.c

[...]

> +static struct platform_driver acpm_tmu_driver = {
> +	.driver = {
> +		.name   = "gs-tmu",

Should it be "gs-tmu" still?
https://lore.kernel.org/linux-samsung-soc/56c1bb6d-54e4-4977-bd88-9ce7a6086b1d@linaro.org/

> +		.pm     = pm_ptr(&acpm_tmu_pm_ops),
> +		.of_match_table = acpm_tmu_match,
> +	},
> +	.probe = acpm_tmu_probe,
> +	.remove = acpm_tmu_remove,
> +};
> +module_platform_driver(acpm_tmu_driver);

Thanks,
Alexey

^ permalink raw reply

* [PATCH v2 0/8] riscv: Add Ssqosid and initial CBQRI resctrl support
From: Drew Fustini @ 2026-06-25  1:38 UTC (permalink / raw)
  To: 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, yunhui cui, Drew Fustini
  Cc: linux-kernel, linux-riscv, x86, devicetree, linux-rt-devel,
	linux-doc

This series adds initial RISC-V QoS support: the Ssqosid extension [1]
(srmcfg CSR), the CBQRI controller interface [2] integrated with resctrl
[3], and a DT-based platform driver for cache controllers. It has been
tested on both the Tenstorrent Ascalon Shared Cache controller and a QEMU
implementation [4].

  qemu-system-riscv64 -M virt,aia=aplic-imsic -nographic -m 1G -smp 8 \
      -kernel arch/riscv/boot/Image \
      -append "root=/dev/vda ro console=ttyS0 rootwait" \
      -drive if=none,file=rootfs.ext2,format=raw,id=hd0 \
      -device virtio-blk-device,drive=hd0 \
      -device riscv.cbqri.capacity,max_mcids=256,max_rcids=64,ncblks=16,mmio_base=0x04820000

Cache allocation can be exercised on the booted system. Mount resctrl
and read the default schemata. The L2 controller has 16 capacity
blocks, so the default capacity bitmask (CBM) is 0xffff:

  # mount -t resctrl resctrl /sys/fs/resctrl
  # cat /sys/fs/resctrl/schemata
  L2:0=ffff

Write a narrower CBM to a new control group and read it back to confirm
the L2 controller applied it:

  # mkdir /sys/fs/resctrl/group0
  # echo "L2:0=ff" > /sys/fs/resctrl/group0/schemata
  # cat /sys/fs/resctrl/group0/schemata
  L2:0=ff

Note that this series only implements support for resctrl L2 and L3
cache resources using CBQRI capacity allocation control. cc_block_mask
maps onto resctrl's existing cbm schema. However, cc_cunits is not
supported as there is no existing equivalent for capacity units in the
resctrl schemata.

I had previously been iterating on an RFC series [5] that did a full
implementation of CBQRI including capacity monitoring, bandwidth
allocation and monitoring. The bandwidth controls for CBQRI do not fit
well into resctrl's existing throttle-based MB schemata. I believe that
the path forward is Reinette's generic schema description proof of
concept [6]. My plan is to rebase the full support of CBQRI onto the
generic schema once it is ready.

This series is based on the linux-next tag next-20260623.

[1] https://github.com/riscv/riscv-ssqosid/releases/tag/v1.0
[2] https://github.com/riscv-non-isa/riscv-cbqri/releases/tag/v1.0
[3] https://docs.kernel.org/filesystems/resctrl.html
[4] https://github.com/tt-fustini/qemu/tree/riscv-cbqri-cache
[5] https://lore.kernel.org/linux-riscv/20260601-ssqosid-cbqri-rqsc-v7-0-v6-16-baf00f50028a@kernel.org/
[6] https://lore.kernel.org/all/aab804b9-e8b5-40ad-a85b-af7033391243@intel.com/

Changes in v2:
--------------
The changes in this revision address the Sashiko review of v1.

- Restore the srmcfg CSR for the current task on CPU_PM_EXIT and
  CPU_PM_ENTER_FAILED, so it is not left configured incorrectly until
  the next context switch.

- Serialize the cbqri_controllers list insert and the boot time walk
  with a mutex, so an asynchronous driver probe cannot corrupt the list.

- Skip a controller at an unsupported cache level instead of aborting
  resctrl setup, so valid L2 and L3 controllers still register.

- RISCV_ISA_SSQOSID selects ARCH_HAS_CPU_RESCTRL and RISCV_CBQRI
  together, so no intermediate commit enables RESCTRL_FS without the
  CBQRI resctrl glue.

- Rename the RISCV_CBQRI_DRIVER to RISCV_CBQRI, since it builds the
  CBQRI core ops and resctrl integration rather than a driver.

- Drop the RISCV_CBQRI_DRIVER_DEBUG Kconfig option and rely on dynamic
  debug to control the pr_debug() output.

- Note: Sashiko flagged the lack of suspend/resume state restore. I will
  not fix that as register state is only lost when the power domain is
  gated, which offlines the harts sharing the cache. resctrl reprograms
  the default capacity mask through the normal control domain online
  path on resume.

Link to v1:
https://lore.kernel.org/all/20260619-dfustini-atl-sc-cbqri-dt-v1-0-e79a7723fab0@kernel.org/

Sashiko review:
https://sashiko.dev/#/patchset/20260619-dfustini-atl-sc-cbqri-dt-v1-0-e79a7723fab0%40kernel.org

---
Drew Fustini (8):
      dt-bindings: riscv: Add Ssqosid extension description
      riscv: Detect the Ssqosid extension
      riscv: Add support for srmcfg CSR from Ssqosid extension
      riscv_cbqri: Add capacity controller probe and allocation device ops
      riscv_cbqri: resctrl: Add cache allocation via capacity block mask
      riscv: Enable resctrl filesystem for Ssqosid
      dt-bindings: riscv: Add generic CBQRI controller binding
      riscv_cbqri: Add CBQRI cache capacity-allocation platform driver

 .../devicetree/bindings/riscv/extensions.yaml      |   6 +
 .../devicetree/bindings/riscv/riscv,cbqri.yaml     |  97 +++
 MAINTAINERS                                        |  15 +
 arch/riscv/Kconfig                                 |  20 +
 arch/riscv/include/asm/csr.h                       |   5 +
 arch/riscv/include/asm/hwcap.h                     |   1 +
 arch/riscv/include/asm/processor.h                 |   3 +
 arch/riscv/include/asm/qos.h                       |  83 +++
 arch/riscv/include/asm/resctrl.h                   | 147 ++++
 arch/riscv/include/asm/switch_to.h                 |   3 +
 arch/riscv/kernel/Makefile                         |   2 +
 arch/riscv/kernel/cpufeature.c                     |   1 +
 arch/riscv/kernel/qos.c                            |  98 +++
 drivers/resctrl/Kconfig                            |  29 +
 drivers/resctrl/Makefile                           |   5 +
 drivers/resctrl/cbqri_capacity.c                   | 132 ++++
 drivers/resctrl/cbqri_devices.c                    | 520 ++++++++++++++
 drivers/resctrl/cbqri_internal.h                   | 107 +++
 drivers/resctrl/cbqri_resctrl.c                    | 779 +++++++++++++++++++++
 include/linux/riscv_cbqri.h                        |  47 ++
 20 files changed, 2100 insertions(+)
---
base-commit: 4e5dfb7c84012007c3c7061126491bbc92d71bf1
change-id: 20260610-dfustini-atl-sc-cbqri-dt-410c8e2711dd

Best regards,
-- 
Drew Fustini <fustini@kernel.org>


^ permalink raw reply

* [PATCH v2 1/8] dt-bindings: riscv: Add Ssqosid extension description
From: Drew Fustini @ 2026-06-25  1:38 UTC (permalink / raw)
  To: 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, yunhui cui, Drew Fustini
  Cc: linux-kernel, linux-riscv, x86, devicetree, linux-rt-devel,
	linux-doc
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-0-2f8049fd902b@kernel.org>

Document the ratified Supervisor-mode Quality of Service ID (Ssqosid)
extension v1.0.

Link: https://github.com/riscv/riscv-ssqosid/releases/tag/v1.0
Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Drew Fustini <fustini@kernel.org>
---
 Documentation/devicetree/bindings/riscv/extensions.yaml | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/Documentation/devicetree/bindings/riscv/extensions.yaml b/Documentation/devicetree/bindings/riscv/extensions.yaml
index 2b0a8a93bb21445e40ec106bdff71f4daf9563f6..1c6f091518d49aca20ecd4838c124b4983f0ef88 100644
--- a/Documentation/devicetree/bindings/riscv/extensions.yaml
+++ b/Documentation/devicetree/bindings/riscv/extensions.yaml
@@ -232,6 +232,12 @@ properties:
             ratified at commit d70011dde6c2 ("Update to ratified state")
             of riscv-j-extension.
 
+        - const: ssqosid
+          description: |
+            The standard Ssqosid extension for Quality of Service ID is
+            ratified as v1.0 in commit d9c616497fde ("Merge pull
+            request #7 from ved-rivos/Ratified") of riscv-ssqosid.
+
         - const: ssstateen
           description: |
             The standard Ssstateen extension for supervisor-mode view of the

-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 2/8] riscv: Detect the Ssqosid extension
From: Drew Fustini @ 2026-06-25  1:38 UTC (permalink / raw)
  To: 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, yunhui cui, Drew Fustini
  Cc: linux-kernel, linux-riscv, x86, devicetree, linux-rt-devel,
	linux-doc
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-0-2f8049fd902b@kernel.org>

Ssqosid is the RISC-V Quality-of-Service (QoS) Identifiers specification
which defines the Supervisor Resource Management Configuration (srmcfg)
register.

Link: https://github.com/riscv/riscv-ssqosid/releases/tag/v1.0
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>
---
 arch/riscv/include/asm/hwcap.h | 1 +
 arch/riscv/kernel/cpufeature.c | 1 +
 2 files changed, 2 insertions(+)

diff --git a/arch/riscv/include/asm/hwcap.h b/arch/riscv/include/asm/hwcap.h
index 7ef8e5f55c8dcf26e3dcc676db32c2f16b7e7f5a..b83dae5cebb99269902279faa3c03fd23f7d4342 100644
--- a/arch/riscv/include/asm/hwcap.h
+++ b/arch/riscv/include/asm/hwcap.h
@@ -112,6 +112,7 @@
 #define RISCV_ISA_EXT_ZCLSD		103
 #define RISCV_ISA_EXT_ZICFILP		104
 #define RISCV_ISA_EXT_ZICFISS		105
+#define RISCV_ISA_EXT_SSQOSID		106
 
 #define RISCV_ISA_EXT_XLINUXENVCFG	127
 
diff --git a/arch/riscv/kernel/cpufeature.c b/arch/riscv/kernel/cpufeature.c
index f46aa5602d74d3322960559fb73bb50152909344..668a7e71ff1c64e8bd36ba6ea94f8a5dd4600016 100644
--- a/arch/riscv/kernel/cpufeature.c
+++ b/arch/riscv/kernel/cpufeature.c
@@ -582,6 +582,7 @@ const struct riscv_isa_ext_data riscv_isa_ext[] = {
 	__RISCV_ISA_EXT_DATA(ssaia, RISCV_ISA_EXT_SSAIA),
 	__RISCV_ISA_EXT_DATA(sscofpmf, RISCV_ISA_EXT_SSCOFPMF),
 	__RISCV_ISA_EXT_SUPERSET(ssnpm, RISCV_ISA_EXT_SSNPM, riscv_xlinuxenvcfg_exts),
+	__RISCV_ISA_EXT_DATA(ssqosid, RISCV_ISA_EXT_SSQOSID),
 	__RISCV_ISA_EXT_DATA(sstc, RISCV_ISA_EXT_SSTC),
 	__RISCV_ISA_EXT_DATA(svade, RISCV_ISA_EXT_SVADE),
 	__RISCV_ISA_EXT_DATA_VALIDATE(svadu, RISCV_ISA_EXT_SVADU, riscv_ext_svadu_validate),

-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 3/8] riscv: Add support for srmcfg CSR from Ssqosid extension
From: Drew Fustini @ 2026-06-25  1:38 UTC (permalink / raw)
  To: 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, yunhui cui, Drew Fustini
  Cc: linux-kernel, linux-riscv, x86, devicetree, linux-rt-devel,
	linux-doc
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-0-2f8049fd902b@kernel.org>

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);
+	}
+}
+
+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


^ permalink raw reply related

* [PATCH v2 4/8] riscv_cbqri: Add capacity controller probe and allocation device ops
From: Drew Fustini @ 2026-06-25  1:38 UTC (permalink / raw)
  To: 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, yunhui cui, Drew Fustini
  Cc: linux-kernel, linux-riscv, x86, devicetree, linux-rt-devel,
	linux-doc
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-0-2f8049fd902b@kernel.org>

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, &reg) < 0) {
+		pr_err_ratelimited("BUSY timeout before starting operation\n");
+		return -EIO;
+	}
+	FIELD_MODIFY(CBQRI_CONTROL_REGISTERS_OP_MASK, &reg, operation);
+	FIELD_MODIFY(CBQRI_CONTROL_REGISTERS_RCID_MASK, &reg, 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, &reg) < 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, &reg) < 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, &reg) < 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


^ permalink raw reply related

* [PATCH v2 5/8] riscv_cbqri: resctrl: Add cache allocation via capacity block mask
From: Drew Fustini @ 2026-06-25  1:38 UTC (permalink / raw)
  To: 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, yunhui cui, Drew Fustini
  Cc: linux-kernel, linux-riscv, x86, devicetree, linux-rt-devel,
	linux-doc
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-0-2f8049fd902b@kernel.org>

Wire CBQRI capacity controllers into resctrl as RDT_RESOURCE_L2 and
RDT_RESOURCE_L3 schemata.

Mismatched CC caps at the same cache level are treated as a fatal
configuration error since fs/resctrl exposes a single per-rid cap
set. Domains are created lazily in the cpuhp online callback so
cpu_mask reflects only currently online CPUs.

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                      |   2 +
 arch/riscv/include/asm/resctrl.h | 147 ++++++++
 drivers/resctrl/Kconfig          |   4 +
 drivers/resctrl/Makefile         |   1 +
 drivers/resctrl/cbqri_resctrl.c  | 779 +++++++++++++++++++++++++++++++++++++++
 5 files changed, 933 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 811c0c9b1fac806945cad0229c5330654420a835..9e1092165046c773771b055869030bc1bdb64b16 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23299,9 +23299,11 @@ R:	yunhui cui <cuiyunhui@bytedance.com>
 L:	linux-riscv@lists.infradead.org
 S:	Supported
 F:	arch/riscv/include/asm/qos.h
+F:	arch/riscv/include/asm/resctrl.h
 F:	arch/riscv/kernel/qos.c
 F:	drivers/resctrl/cbqri_devices.c
 F:	drivers/resctrl/cbqri_internal.h
+F:	drivers/resctrl/cbqri_resctrl.c
 F:	include/linux/riscv_cbqri.h
 
 RISC-V RPMI AND MPXY DRIVERS
diff --git a/arch/riscv/include/asm/resctrl.h b/arch/riscv/include/asm/resctrl.h
new file mode 100644
index 0000000000000000000000000000000000000000..b08f4e12f7aa98663de75ec073ccb8090594583e
--- /dev/null
+++ b/arch/riscv/include/asm/resctrl.h
@@ -0,0 +1,147 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _ASM_RISCV_RESCTRL_H
+#define _ASM_RISCV_RESCTRL_H
+
+#include <linux/resctrl_types.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+
+#include <asm/qos.h>
+
+struct rdt_resource;
+
+/*
+ * Sentinel "no CLOSID assigned" used by resctrl_arch_rmid_idx_decode().
+ * fs/resctrl treats this opaquely. CBQRI uses MCID directly as the linear
+ * rmid index, so closid is unused on decode.
+ */
+#define RISCV_RESCTRL_EMPTY_CLOSID	((u32)~0)
+
+/*
+ * Terminology mapping between x86 (Intel RDT/AMD QoS) and RISC-V:
+ *
+ *  CLOSID on x86 is RCID on RISC-V
+ *    RMID on x86 is MCID on RISC-V
+ *     CDP on x86 is AT (access type) on RISC-V
+ */
+
+/**
+ * resctrl_arch_alloc_capable() - any CBQRI controller exposes resctrl alloc
+ *
+ * Returns true once at least one CBQRI controller has successfully probed for
+ * a resctrl-exposed cache capacity allocation feature. Only meaningful after
+ * cbqri_resctrl_setup() runs at late_initcall.
+ */
+bool resctrl_arch_alloc_capable(void);
+
+/**
+ * resctrl_arch_mon_capable() - any CBQRI controller exposes resctrl monitoring
+ *
+ * The CBQRI driver implements capacity allocation only and wires up no
+ * monitoring events, so this always returns false. fs/resctrl references it
+ * unconditionally, hence the stub.
+ */
+bool resctrl_arch_mon_capable(void);
+
+/**
+ * resctrl_arch_rmid_idx_encode() - encode (RCID, MCID) into a linear index
+ * @closid: RCID (resource control id)
+ * @rmid:   MCID (monitoring counter id)
+ *
+ * RISC-V uses MCID directly as the linear index into per-RMID arrays
+ * managed by fs/resctrl, since CBQRI controllers admit any MCID for any
+ * RCID. closid is unused here. CDP is encoded via the AT field on each
+ * CBQRI op rather than via the index.
+ */
+u32  resctrl_arch_rmid_idx_encode(u32 closid, u32 rmid);
+
+/**
+ * resctrl_arch_rmid_idx_decode() - inverse of resctrl_arch_rmid_idx_encode()
+ * @idx:    linear index
+ * @closid: out: always RISCV_RESCTRL_EMPTY_CLOSID
+ * @rmid:   out: the MCID that @idx encodes
+ */
+void resctrl_arch_rmid_idx_decode(u32 idx, u32 *closid, u32 *rmid);
+
+/**
+ * resctrl_arch_set_cpu_default_closid_rmid() - install per-CPU srmcfg default
+ * @cpu:    CPU number
+ * @closid: RCID to use when no task is matched
+ * @rmid:   MCID to use when no task is matched
+ *
+ * Sets the per-CPU cpu_srmcfg_default so __switch_to_srmcfg() can fall back
+ * to the CPU's default RCID/MCID for default-group tasks (those whose
+ * thread.srmcfg encodes to 0, i.e. closid == RESCTRL_RESERVED_CLOSID and
+ * rmid == RESCTRL_RESERVED_RMID). Implements resctrl allocation rule 2
+ * ("CPU default") on RISC-V.
+ */
+void resctrl_arch_set_cpu_default_closid_rmid(int cpu, u32 closid, u32 rmid);
+
+/**
+ * resctrl_arch_sched_in() - context-switch hook to install task RCID/MCID
+ * @tsk: the task being scheduled in
+ *
+ * Called from finish_task_switch() to write tsk->thread.srmcfg into the
+ * srmcfg CSR. Tasks tagged with RISCV_RESCTRL_EMPTY_CLOSID inherit the
+ * per-CPU default set via resctrl_arch_set_cpu_default_closid_rmid().
+ */
+void resctrl_arch_sched_in(struct task_struct *tsk);
+
+/**
+ * resctrl_arch_set_closid_rmid() - tag a task with an RCID/MCID
+ * @tsk:    task to tag
+ * @closid: RCID to install
+ * @rmid:   MCID to install
+ *
+ * Updates tsk->thread.srmcfg with the encoded (RCID, MCID) pair. The new
+ * value takes effect on the next resctrl_arch_sched_in() for this task.
+ */
+void resctrl_arch_set_closid_rmid(struct task_struct *tsk, u32 closid, u32 rmid);
+
+/**
+ * resctrl_arch_match_closid() - test whether a task carries a given RCID
+ * @tsk:    task
+ * @closid: RCID
+ */
+bool resctrl_arch_match_closid(struct task_struct *tsk, u32 closid);
+
+/**
+ * resctrl_arch_match_rmid() - test whether a task carries a given (RCID, MCID)
+ * @tsk:    task
+ * @closid: RCID
+ * @rmid:   MCID
+ */
+bool resctrl_arch_match_rmid(struct task_struct *tsk, u32 closid, u32 rmid);
+
+/**
+ * resctrl_arch_mon_ctx_alloc() - allocate per-monitor-event arch context
+ * @r:     resctrl resource being monitored
+ * @evtid: which monitor event needs context
+ *
+ * The CBQRI driver implements no monitoring events, so there is no per-event
+ * context to allocate and the stub returns NULL. fs/resctrl references it
+ * unconditionally before checking resctrl_arch_mon_capable().
+ */
+void *resctrl_arch_mon_ctx_alloc(struct rdt_resource *r, enum resctrl_event_id evtid);
+
+/**
+ * resctrl_arch_mon_ctx_free() - release context returned by mon_ctx_alloc()
+ * @r:            resctrl resource
+ * @evtid:        monitor event id
+ * @arch_mon_ctx: pointer returned by resctrl_arch_mon_ctx_alloc()
+ */
+void resctrl_arch_mon_ctx_free(struct rdt_resource *r, enum resctrl_event_id evtid,
+			       void *arch_mon_ctx);
+
+static inline unsigned int resctrl_arch_round_mon_val(unsigned int val)
+{
+	return val;
+}
+
+/* Not needed for RISC-V */
+static inline void resctrl_arch_enable_mon(void) { }
+static inline void resctrl_arch_disable_mon(void) { }
+static inline void resctrl_arch_enable_alloc(void) { }
+static inline void resctrl_arch_disable_alloc(void) { }
+
+#endif /* _ASM_RISCV_RESCTRL_H */
diff --git a/drivers/resctrl/Kconfig b/drivers/resctrl/Kconfig
index 92b9c82cf9f397437d28006e79d40c2d9b384eb0..f8566c003d49570b844908d57c231d73c3bb0f6e 100644
--- a/drivers/resctrl/Kconfig
+++ b/drivers/resctrl/Kconfig
@@ -42,3 +42,7 @@ menuconfig RISCV_CBQRI
 if RISCV_CBQRI
 
 endif
+
+config RISCV_CBQRI_RESCTRL_FS
+	bool
+	default y if RISCV_CBQRI && RESCTRL_FS
diff --git a/drivers/resctrl/Makefile b/drivers/resctrl/Makefile
index 4d8a2c4b5627144a651da007174d4d9df6171330..a7631712dba9e1c9dd2a0b07a089204671f85d1f 100644
--- a/drivers/resctrl/Makefile
+++ b/drivers/resctrl/Makefile
@@ -6,3 +6,4 @@ ccflags-$(CONFIG_ARM64_MPAM_DRIVER_DEBUG)	+= -DDEBUG
 
 obj-$(CONFIG_RISCV_CBQRI)			+= cbqri.o
 cbqri-y						+= cbqri_devices.o
+cbqri-$(CONFIG_RISCV_CBQRI_RESCTRL_FS)		+= cbqri_resctrl.o
diff --git a/drivers/resctrl/cbqri_resctrl.c b/drivers/resctrl/cbqri_resctrl.c
new file mode 100644
index 0000000000000000000000000000000000000000..2f31baff032b61cbbe9f7a67a85e18f6fb90f7a4
--- /dev/null
+++ b/drivers/resctrl/cbqri_resctrl.c
@@ -0,0 +1,779 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
+
+#include <linux/bitfield.h>
+#include <linux/cacheinfo.h>
+#include <linux/riscv_cbqri.h>
+#include <linux/cpu.h>
+#include <linux/cpufeature.h>
+#include <linux/cpuhotplug.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/resctrl.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include <asm/csr.h>
+#include <asm/qos.h>
+
+#include "cbqri_internal.h"
+
+struct cbqri_resctrl_res {
+	struct cbqri_controller *ctrl;
+	struct rdt_resource     resctrl_res;
+	bool                    cdp_enabled;
+};
+
+struct cbqri_resctrl_dom {
+	struct rdt_ctrl_domain  resctrl_ctrl_dom;
+	struct cbqri_controller *hw_ctrl;
+};
+
+static struct cbqri_resctrl_res cbqri_resctrl_resources[RDT_NUM_RESOURCES];
+
+static bool exposed_alloc_capable;
+
+/* Protects ctrl_domain list mutations across CPU hotplug. */
+static DEFINE_MUTEX(cbqri_domain_list_lock);
+
+static struct rdt_ctrl_domain *
+cbqri_find_ctrl_domain(struct list_head *h, int id)
+{
+	struct rdt_domain_hdr *hdr = resctrl_find_domain(h, id, NULL);
+
+	return hdr ? container_of(hdr, struct rdt_ctrl_domain, hdr) : NULL;
+}
+
+/* Map a hardware cache level to its resctrl resource id, or -ENODEV. */
+static int cbqri_cache_level_to_rid(u32 cache_level)
+{
+	switch (cache_level) {
+	case 2:
+		return RDT_RESOURCE_L2;
+	case 3:
+		return RDT_RESOURCE_L3;
+	default:
+		return -ENODEV;
+	}
+}
+
+static int cbqri_apply_cache_config_dom(struct cbqri_resctrl_dom *hw_dom,
+					struct rdt_resource *r,
+					u32 closid, enum resctrl_conf_type t,
+					u64 cbm)
+{
+	struct cbqri_resctrl_res *hw_res =
+		container_of(r, struct cbqri_resctrl_res, resctrl_res);
+	struct cbqri_cc_config cfg = {
+		.cbm = cbm,
+		.at = (t == CDP_CODE) ? CBQRI_CONTROL_REGISTERS_AT_CODE :
+					CBQRI_CONTROL_REGISTERS_AT_DATA,
+		.cdp_enabled = hw_res->cdp_enabled,
+	};
+
+	return cbqri_apply_cache_config(hw_dom->hw_ctrl, closid, &cfg);
+}
+
+bool resctrl_arch_alloc_capable(void)
+{
+	return exposed_alloc_capable;
+}
+
+bool resctrl_arch_mon_capable(void)
+{
+	return false;
+}
+
+bool resctrl_arch_get_cdp_enabled(enum resctrl_res_level rid)
+{
+	if (rid != RDT_RESOURCE_L2 && rid != RDT_RESOURCE_L3)
+		return false;
+	return cbqri_resctrl_resources[rid].cdp_enabled;
+}
+
+int resctrl_arch_set_cdp_enabled(enum resctrl_res_level rid, bool enable)
+{
+	struct cbqri_resctrl_res *cbqri_res;
+
+	if (rid != RDT_RESOURCE_L2 && rid != RDT_RESOURCE_L3)
+		return -ENODEV;
+
+	cbqri_res = &cbqri_resctrl_resources[rid];
+	if (!cbqri_res->resctrl_res.cdp_capable)
+		return -ENODEV;
+
+	cbqri_res->cdp_enabled = enable;
+	return 0;
+}
+
+struct rdt_resource *resctrl_arch_get_resource(enum resctrl_res_level l)
+{
+	if (l >= RDT_NUM_RESOURCES)
+		return NULL;
+
+	return &cbqri_resctrl_resources[l].resctrl_res;
+}
+
+/*
+ * fs/resctrl unconditionally references the symbols below before checking
+ * mon_capable. They are stubs for features CBQRI does not yet support.
+ */
+bool resctrl_arch_is_evt_configurable(enum resctrl_event_id evt)
+{
+	return false;
+}
+
+void *resctrl_arch_mon_ctx_alloc(struct rdt_resource *r,
+				 enum resctrl_event_id evtid)
+{
+	return NULL;
+}
+
+void resctrl_arch_mon_ctx_free(struct rdt_resource *r,
+			       enum resctrl_event_id evtid, void *arch_mon_ctx)
+{
+}
+
+void resctrl_arch_config_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+			      enum resctrl_event_id evtid, u32 rmid, u32 closid,
+			      u32 cntr_id, bool assign)
+{
+}
+
+int resctrl_arch_cntr_read(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+			   u32 unused, u32 rmid, int cntr_id,
+			   enum resctrl_event_id eventid, u64 *val)
+{
+	return -EOPNOTSUPP;
+}
+
+bool resctrl_arch_mbm_cntr_assign_enabled(struct rdt_resource *r)
+{
+	return false;
+}
+
+int resctrl_arch_mbm_cntr_assign_set(struct rdt_resource *r, bool enable)
+{
+	return -EOPNOTSUPP;
+}
+
+void resctrl_arch_reset_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+			     u32 unused, u32 rmid, int cntr_id,
+			     enum resctrl_event_id eventid)
+{
+}
+
+bool resctrl_arch_get_io_alloc_enabled(struct rdt_resource *r)
+{
+	return false;
+}
+
+int resctrl_arch_io_alloc_enable(struct rdt_resource *r, bool enable)
+{
+	return -EOPNOTSUPP;
+}
+
+void resctrl_arch_mon_event_config_read(void *info)
+{
+}
+
+void resctrl_arch_mon_event_config_write(void *info)
+{
+}
+
+void resctrl_arch_reset_rmid_all(struct rdt_resource *r, struct rdt_l3_mon_domain *d)
+{
+}
+
+void resctrl_arch_reset_rmid(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+			     u32 unused, u32 rmid, enum resctrl_event_id eventid)
+{
+}
+
+int resctrl_arch_rmid_read(struct rdt_resource *r, struct rdt_domain_hdr *hdr,
+			   u32 closid, u32 rmid, enum resctrl_event_id eventid,
+			   void *arch_priv, u64 *val, void *arch_mon_ctx)
+{
+	return -ENODATA;
+}
+
+/*
+ * Note about terminology between x86 (Intel RDT/AMD QoS) and RISC-V:
+ *   CLOSID on x86 is RCID on RISC-V
+ *     RMID on x86 is MCID on RISC-V
+ */
+u32 resctrl_arch_get_num_closid(struct rdt_resource *res)
+{
+	struct cbqri_resctrl_res *hw_res;
+
+	hw_res = container_of(res, struct cbqri_resctrl_res, resctrl_res);
+
+	if (!hw_res->ctrl)
+		return 0;
+
+	return hw_res->ctrl->rcid_count;
+}
+
+u32 resctrl_arch_system_num_rmid_idx(void)
+{
+	return 1;
+}
+
+u32 resctrl_arch_rmid_idx_encode(u32 closid, u32 rmid)
+{
+	return rmid;
+}
+
+void resctrl_arch_rmid_idx_decode(u32 idx, u32 *closid, u32 *rmid)
+{
+	*closid = RISCV_RESCTRL_EMPTY_CLOSID;
+	*rmid = idx;
+}
+
+void resctrl_arch_set_cpu_default_closid_rmid(int cpu, u32 closid, u32 rmid)
+{
+	u32 srmcfg = FIELD_PREP(SRMCFG_RCID_MASK, closid) |
+		     FIELD_PREP(SRMCFG_MCID_MASK, rmid);
+
+	WRITE_ONCE(per_cpu(cpu_srmcfg_default, cpu), srmcfg);
+}
+
+void resctrl_arch_sched_in(struct task_struct *tsk)
+{
+	__switch_to_srmcfg(tsk);
+}
+
+void resctrl_arch_set_closid_rmid(struct task_struct *tsk, u32 closid, u32 rmid)
+{
+	u32 srmcfg = FIELD_PREP(SRMCFG_RCID_MASK, closid) |
+		     FIELD_PREP(SRMCFG_MCID_MASK, rmid);
+
+	WRITE_ONCE(tsk->thread.srmcfg, srmcfg);
+}
+
+void resctrl_arch_sync_cpu_closid_rmid(void *info)
+{
+	struct resctrl_cpu_defaults *r = info;
+
+	lockdep_assert_preemption_disabled();
+
+	if (r) {
+		resctrl_arch_set_cpu_default_closid_rmid(smp_processor_id(),
+							 r->closid, r->rmid);
+	}
+
+	resctrl_arch_sched_in(current);
+}
+
+bool resctrl_arch_match_closid(struct task_struct *tsk, u32 closid)
+{
+	return FIELD_GET(SRMCFG_RCID_MASK, READ_ONCE(tsk->thread.srmcfg)) == closid;
+}
+
+bool resctrl_arch_match_rmid(struct task_struct *tsk, u32 closid, u32 rmid)
+{
+	return FIELD_GET(SRMCFG_MCID_MASK, READ_ONCE(tsk->thread.srmcfg)) == rmid;
+}
+
+void resctrl_arch_pre_mount(void)
+{
+	/* All controllers discovered at boot via late_initcall. Nothing to do. */
+}
+
+int resctrl_arch_update_one(struct rdt_resource *r, struct rdt_ctrl_domain *d,
+			    u32 closid, enum resctrl_conf_type t, u32 cfg_val)
+{
+	struct cbqri_resctrl_dom *dom;
+
+	dom = container_of(d, struct cbqri_resctrl_dom, resctrl_ctrl_dom);
+
+	if (!r->alloc_capable)
+		return -EINVAL;
+
+	switch (r->rid) {
+	case RDT_RESOURCE_L2:
+	case RDT_RESOURCE_L3:
+		return cbqri_apply_cache_config_dom(dom, r, closid, t, cfg_val);
+	default:
+		return -EINVAL;
+	}
+}
+
+int resctrl_arch_update_domains(struct rdt_resource *r, u32 closid)
+{
+	struct resctrl_staged_config *cfg;
+	enum resctrl_conf_type t;
+	struct rdt_ctrl_domain *d;
+	int err = 0;
+
+	/* Walking r->ctrl_domains, ensure it can't race with cpuhp */
+	lockdep_assert_cpus_held();
+
+	list_for_each_entry(d, &r->ctrl_domains, hdr.list) {
+		for (t = 0; t < CDP_NUM_TYPES; t++) {
+			cfg = &d->staged_config[t];
+			if (!cfg->have_new_ctrl)
+				continue;
+			err = resctrl_arch_update_one(r, d, closid, t, cfg->new_ctrl);
+			if (err)
+				return err;
+		}
+	}
+	return err;
+}
+
+u32 resctrl_arch_get_config(struct rdt_resource *r, struct rdt_ctrl_domain *d,
+			    u32 closid, enum resctrl_conf_type type)
+{
+	struct cbqri_resctrl_dom *hw_dom;
+	struct cbqri_controller *ctrl;
+	u32 at;
+	u32 val;
+	int err;
+
+	hw_dom = container_of(d, struct cbqri_resctrl_dom, resctrl_ctrl_dom);
+	ctrl = hw_dom->hw_ctrl;
+	val = resctrl_get_default_ctrl(r);
+
+	if (!r->alloc_capable)
+		return val;
+
+	switch (r->rid) {
+	case RDT_RESOURCE_L2:
+	case RDT_RESOURCE_L3:
+		at = (type == CDP_CODE) ? CBQRI_CONTROL_REGISTERS_AT_CODE :
+					  CBQRI_CONTROL_REGISTERS_AT_DATA;
+		err = cbqri_read_cache_config(ctrl, closid, at, &val);
+		if (err < 0)
+			val = resctrl_get_default_ctrl(r);
+		break;
+	default:
+		break;
+	}
+
+	return val;
+}
+
+void resctrl_arch_reset_all_ctrls(struct rdt_resource *r)
+{
+	struct cbqri_resctrl_res *hw_res;
+	struct rdt_ctrl_domain *d;
+	enum resctrl_conf_type t;
+	u32 default_ctrl;
+	int i;
+
+	lockdep_assert_cpus_held();
+
+	hw_res = container_of(r, struct cbqri_resctrl_res, resctrl_res);
+	default_ctrl = resctrl_get_default_ctrl(r);
+
+	if (!hw_res->ctrl)
+		return;
+
+	list_for_each_entry(d, &r->ctrl_domains, hdr.list) {
+		for (i = 0; i < hw_res->ctrl->rcid_count; i++) {
+			for (t = 0; t < CDP_NUM_TYPES; t++) {
+				int rerr;
+
+				rerr = resctrl_arch_update_one(r, d, i, t, default_ctrl);
+				if (rerr)
+					pr_err_ratelimited("rid=%d reset RCID %u type %u failed (%d)\n",
+							   r->rid, i, t, rerr);
+			}
+		}
+	}
+}
+
+static struct rdt_ctrl_domain *cbqri_new_domain(struct cbqri_controller *ctrl)
+{
+	struct cbqri_resctrl_dom *hw_dom;
+	struct rdt_ctrl_domain *domain;
+
+	hw_dom = kzalloc_obj(*hw_dom, GFP_KERNEL);
+	if (!hw_dom)
+		return NULL;
+
+	hw_dom->hw_ctrl = ctrl;
+	domain = &hw_dom->resctrl_ctrl_dom;
+
+	INIT_LIST_HEAD(&domain->hdr.list);
+
+	return domain;
+}
+
+static int cbqri_init_domain_ctrlval(struct rdt_resource *r, struct rdt_ctrl_domain *d)
+{
+	struct cbqri_resctrl_res *hw_res;
+	enum resctrl_conf_type t;
+	int err = 0;
+	int i;
+
+	hw_res = container_of(r, struct cbqri_resctrl_res, resctrl_res);
+
+	for (i = 0; i < hw_res->ctrl->rcid_count; i++) {
+		/*
+		 * Seed both DATA and CODE staged slots so a later mount
+		 * with -o cdp does not see stale CODE values.
+		 * On non-AT controllers cbqri_cc_alloc_op() masks AT to 0
+		 * so all three iterations land on the same hardware state.
+		 * The redundant writes are harmless.
+		 */
+		for (t = 0; t < CDP_NUM_TYPES; t++) {
+			err = resctrl_arch_update_one(r, d, i, t,
+						      resctrl_get_default_ctrl(r));
+			if (err)
+				return err;
+		}
+	}
+	return 0;
+}
+
+/*
+ * Walk cbqri_controllers and pick one capacity controller (CC) per cache
+ * level (L2/L3) to back the corresponding RDT_RESOURCE_L*. When more than
+ * one CC sits at the same level (e.g. one per socket), they must agree on
+ * rcid_count / ncblks / alloc_capable. A mismatch is fatal because resctrl
+ * exposes a single set of caps per rid. The first matching controller wins.
+ */
+static int cbqri_resctrl_pick_caches(void)
+{
+	struct cbqri_controller *ctrl;
+	int ret = 0;
+
+	mutex_lock(&cbqri_controllers_lock);
+
+	list_for_each_entry(ctrl, &cbqri_controllers, list) {
+		struct cbqri_resctrl_res *cbqri_res;
+		int rid;
+
+		if (ctrl->type != CBQRI_CONTROLLER_TYPE_CAPACITY)
+			continue;
+		if (!ctrl->alloc_capable)
+			continue;
+
+		rid = cbqri_cache_level_to_rid(ctrl->cache.cache_level);
+		if (rid < 0) {
+			pr_info("skipping controller at unsupported cache level %u\n",
+				ctrl->cache.cache_level);
+			continue;
+		}
+
+		cbqri_res = &cbqri_resctrl_resources[rid];
+		if (cbqri_res->ctrl) {
+			/*
+			 * CCs at the same cache level must agree on every cap
+			 * resctrl exposes globally. Reject mismatches at pick
+			 * time so the inconsistency is visible at boot.
+			 */
+			if (cbqri_res->ctrl->rcid_count != ctrl->rcid_count ||
+			    cbqri_res->ctrl->cc.ncblks != ctrl->cc.ncblks ||
+			    cbqri_res->ctrl->cc.supports_alloc_at_code !=
+				    ctrl->cc.supports_alloc_at_code ||
+			    cbqri_res->ctrl->alloc_capable != ctrl->alloc_capable) {
+				pr_err("L%d controllers have mismatched capabilities\n",
+				       ctrl->cache.cache_level);
+				ret = -EINVAL;
+				break;
+			}
+			continue;
+		}
+
+		cbqri_res->ctrl = ctrl;
+	}
+
+	mutex_unlock(&cbqri_controllers_lock);
+	return ret;
+}
+
+/*
+ * Fill the rdt_resource fields for one picked rid. An rid with no picked
+ * controller is left untouched so it stays out of resctrl_arch_get_resource().
+ */
+static void cbqri_resctrl_control_init(struct cbqri_resctrl_res *cbqri_res)
+{
+	struct cbqri_controller *ctrl = cbqri_res->ctrl;
+	struct rdt_resource *res = &cbqri_res->resctrl_res;
+
+	if (!ctrl)
+		return;
+
+	switch (res->rid) {
+	case RDT_RESOURCE_L2:
+	case RDT_RESOURCE_L3:
+		res->name = (res->rid == RDT_RESOURCE_L2) ? "L2" : "L3";
+		res->schema_fmt = RESCTRL_SCHEMA_BITMAP;
+		res->ctrl_scope = (res->rid == RDT_RESOURCE_L2) ?
+				    RESCTRL_L2_CACHE : RESCTRL_L3_CACHE;
+		res->cache.cbm_len = ctrl->cc.ncblks;
+		res->cache.shareable_bits = 0;
+		res->cache.min_cbm_bits = 1;
+		res->cache.arch_has_sparse_bitmasks = false;
+		res->cdp_capable = ctrl->cc.supports_alloc_at_code;
+		res->alloc_capable = ctrl->alloc_capable;
+		INIT_LIST_HEAD(&res->ctrl_domains);
+		INIT_LIST_HEAD(&res->mon_domains);
+		break;
+	default:
+		break;
+	}
+}
+
+static void cbqri_resctrl_accumulate_caps(void)
+{
+	int rid;
+
+	for (rid = 0; rid < RDT_NUM_RESOURCES; rid++) {
+		struct cbqri_resctrl_res *hw_res = &cbqri_resctrl_resources[rid];
+
+		if (!hw_res->ctrl)
+			continue;
+		if (hw_res->ctrl->alloc_capable)
+			exposed_alloc_capable = true;
+	}
+}
+
+/*
+ * Create, list-insert, and online a fresh ctrl_domain backing ctrl on
+ * resource res, seeded with cpu and identified by dom_id. Caller must
+ * hold cbqri_domain_list_lock and must have already verified that no
+ * existing ctrl_domain on res carries this id.
+ */
+static struct rdt_ctrl_domain *cbqri_create_ctrl_domain(struct cbqri_controller *ctrl,
+							struct rdt_resource *res,
+							unsigned int cpu, int dom_id)
+{
+	struct rdt_ctrl_domain *domain;
+	struct list_head *pos = NULL;
+	int err;
+
+	domain = cbqri_new_domain(ctrl);
+	if (!domain)
+		return ERR_PTR(-ENOMEM);
+
+	cpumask_set_cpu(cpu, &domain->hdr.cpu_mask);
+	domain->hdr.id = dom_id;
+	domain->hdr.type = RESCTRL_CTRL_DOMAIN;
+
+	err = cbqri_init_domain_ctrlval(res, domain);
+	if (err) {
+		kfree(container_of(domain, struct cbqri_resctrl_dom,
+				   resctrl_ctrl_dom));
+		return ERR_PTR(err);
+	}
+
+	/* Insert sorted by id so user-visible ordering is deterministic. */
+	resctrl_find_domain(&res->ctrl_domains, dom_id, &pos);
+	list_add_tail(&domain->hdr.list, pos);
+
+	resctrl_online_ctrl_domain(res, domain);
+
+	return domain;
+}
+
+static int cbqri_attach_cpu_to_cap_ctrl(struct cbqri_controller *ctrl,
+					unsigned int cpu)
+{
+	struct cbqri_resctrl_res *hw_res;
+	struct rdt_ctrl_domain *domain;
+	struct rdt_resource *res;
+	int dom_id;
+	int rid;
+
+	rid = cbqri_cache_level_to_rid(ctrl->cache.cache_level);
+	if (rid < 0)
+		return 0;
+	hw_res = &cbqri_resctrl_resources[rid];
+
+	if (!hw_res->ctrl)
+		return 0;
+
+	res = &hw_res->resctrl_res;
+	dom_id = ctrl->cache.cache_id;
+
+	domain = cbqri_find_ctrl_domain(&res->ctrl_domains, dom_id);
+	if (domain) {
+		cpumask_set_cpu(cpu, &domain->hdr.cpu_mask);
+		return 0;
+	}
+
+	domain = cbqri_create_ctrl_domain(ctrl, res, cpu, dom_id);
+	if (IS_ERR(domain))
+		return PTR_ERR(domain);
+
+	return 0;
+}
+
+static void cbqri_detach_cpu_from_ctrl_domains(struct rdt_resource *res,
+					       unsigned int cpu)
+{
+	struct rdt_ctrl_domain *domain, *tmp;
+
+	list_for_each_entry_safe(domain, tmp, &res->ctrl_domains, hdr.list) {
+		if (!cpumask_test_cpu(cpu, &domain->hdr.cpu_mask))
+			continue;
+		cpumask_clear_cpu(cpu, &domain->hdr.cpu_mask);
+		if (cpumask_empty(&domain->hdr.cpu_mask)) {
+			resctrl_offline_ctrl_domain(res, domain);
+			list_del(&domain->hdr.list);
+			kfree(container_of(domain, struct cbqri_resctrl_dom,
+					   resctrl_ctrl_dom));
+		}
+	}
+}
+
+/*
+ * Remove a CPU from every domain it was attached to. The per-resource
+ * detach helpers act only when the CPU is set in a domain's mask, so this
+ * is idempotent and undoes a partial online attach as well as a full
+ * offline. Caller holds cbqri_domain_list_lock.
+ */
+static void cbqri_detach_cpu_from_all_ctrls(unsigned int cpu)
+{
+	int rid;
+
+	lockdep_assert_held(&cbqri_domain_list_lock);
+
+	for (rid = 0; rid < RDT_NUM_RESOURCES; rid++) {
+		struct cbqri_resctrl_res *hw_res = &cbqri_resctrl_resources[rid];
+
+		if (!hw_res->ctrl)
+			continue;
+		cbqri_detach_cpu_from_ctrl_domains(&hw_res->resctrl_res, cpu);
+	}
+}
+
+/*
+ * Attach a CPU to every controller that claims it. On failure, detach the
+ * CPU from everything attached so far: the cpuhp core does not run this
+ * state's offline teardown when its startup fails, so a partial attach
+ * would otherwise leak into the domain cpu_masks. Caller holds
+ * cbqri_domain_list_lock.
+ */
+static int cbqri_attach_cpu_to_all_ctrls(unsigned int cpu)
+{
+	struct cbqri_controller *ctrl;
+	int err = 0;
+
+	lockdep_assert_held(&cbqri_domain_list_lock);
+
+	list_for_each_entry(ctrl, &cbqri_controllers, list) {
+		if (ctrl->type != CBQRI_CONTROLLER_TYPE_CAPACITY)
+			continue;
+		if (!cpumask_test_cpu(cpu, &ctrl->cache.cpu_mask))
+			continue;
+		if (!ctrl->alloc_capable)
+			continue;
+
+		err = cbqri_attach_cpu_to_cap_ctrl(ctrl, cpu);
+		if (err) {
+			cbqri_detach_cpu_from_all_ctrls(cpu);
+			break;
+		}
+	}
+
+	return err;
+}
+
+static bool cbqri_resctrl_inited;
+
+static void cbqri_resctrl_teardown(void)
+{
+	int rid;
+
+	if (!cbqri_resctrl_inited)
+		return;
+
+	resctrl_exit();
+
+	for (rid = 0; rid < RDT_NUM_RESOURCES; rid++) {
+		struct cbqri_resctrl_res *hw_res = &cbqri_resctrl_resources[rid];
+
+		hw_res->ctrl = NULL;
+		hw_res->cdp_enabled = false;
+	}
+	exposed_alloc_capable = false;
+	cbqri_resctrl_inited = false;
+}
+
+static int cbqri_resctrl_setup(void)
+{
+	int rid;
+	int err;
+
+	for (rid = 0; rid < RDT_NUM_RESOURCES; rid++)
+		cbqri_resctrl_resources[rid].resctrl_res.rid = rid;
+
+	err = cbqri_resctrl_pick_caches();
+	if (err)
+		return err;
+
+	for (rid = 0; rid < RDT_NUM_RESOURCES; rid++)
+		cbqri_resctrl_control_init(&cbqri_resctrl_resources[rid]);
+
+	cbqri_resctrl_accumulate_caps();
+
+	if (!exposed_alloc_capable) {
+		pr_debug("no resctrl-capable CBQRI controllers found\n");
+		return -ENODEV;
+	}
+
+	err = resctrl_init();
+	if (err)
+		return err;
+
+	cbqri_resctrl_inited = true;
+	return 0;
+}
+
+static int cbqri_resctrl_online_cpu(unsigned int cpu)
+{
+	int err;
+
+	mutex_lock(&cbqri_domain_list_lock);
+	err = cbqri_attach_cpu_to_all_ctrls(cpu);
+	mutex_unlock(&cbqri_domain_list_lock);
+	if (err)
+		return err;
+
+	/*
+	 * Seed the per-CPU default RCID/MCID to the reserved (0, 0) pair and
+	 * notify the resctrl core so it tracks this CPU in the default group.
+	 */
+	resctrl_arch_set_cpu_default_closid_rmid(cpu, 0, 0);
+	resctrl_online_cpu(cpu);
+	return 0;
+}
+
+static int cbqri_resctrl_offline_cpu(unsigned int cpu)
+{
+	resctrl_offline_cpu(cpu);
+
+	mutex_lock(&cbqri_domain_list_lock);
+	cbqri_detach_cpu_from_all_ctrls(cpu);
+	mutex_unlock(&cbqri_domain_list_lock);
+	return 0;
+}
+
+static int __init cbqri_arch_late_init(void)
+{
+	int err;
+
+	if (!riscv_isa_extension_available(NULL, SSQOSID))
+		return -ENODEV;
+
+	err = cbqri_resctrl_setup();
+	if (err)
+		return err;
+
+	err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "cbqri:online",
+				cbqri_resctrl_online_cpu,
+				cbqri_resctrl_offline_cpu);
+	if (err < 0) {
+		cbqri_resctrl_teardown();
+		return err;
+	}
+
+	return 0;
+}
+late_initcall(cbqri_arch_late_init);

-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 6/8] riscv: Enable resctrl filesystem for Ssqosid
From: Drew Fustini @ 2026-06-25  1:38 UTC (permalink / raw)
  To: 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, yunhui cui, Drew Fustini
  Cc: linux-kernel, linux-riscv, x86, devicetree, linux-rt-devel,
	linux-doc
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-0-2f8049fd902b@kernel.org>

RISCV_ISA_SSQOSID selects RISCV_CBQRI unconditionally.

The resctrl filesystem integration is gated separately by
RISCV_CBQRI_RESCTRL_FS, a silent option that defaults to y when both
RISCV_CBQRI and RESCTRL_FS are enabled. Enabling the resctrl filesystem
itself stays a user choice via the standard fs/Kconfig MISC_FILESYSTEMS
menu.

Signed-off-by: Drew Fustini <fustini@kernel.org>
---
 arch/riscv/Kconfig | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
index ee586925f97227668c228b5481c05a2f914d928c..9c28bcbc29dc94fe9dd19b8a5c91816a2c75123e 100644
--- a/arch/riscv/Kconfig
+++ b/arch/riscv/Kconfig
@@ -594,6 +594,8 @@ config RISCV_ISA_SSQOSID
 	bool "Ssqosid extension support for supervisor mode Quality of Service ID"
 	depends on 64BIT
 	default n
+	select ARCH_HAS_CPU_RESCTRL
+	select RISCV_CBQRI
 	help
 	  Adds support for the Ssqosid ISA extension (Supervisor-mode
 	  Quality of Service ID).

-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 7/8] dt-bindings: riscv: Add generic CBQRI controller binding
From: Drew Fustini @ 2026-06-25  1:38 UTC (permalink / raw)
  To: 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, yunhui cui, Drew Fustini
  Cc: linux-kernel, linux-riscv, x86, devicetree, linux-rt-devel,
	linux-doc
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-0-2f8049fd902b@kernel.org>

Document the generic compatibles for capacity and bandwidth controllers
that implement the RISC-V CBQRI specification. The binding also
describes the common riscv,cbqri-rcid and riscv,cbqri-mcid properties,
and the optional riscv,cbqri-cache phandle that links a capacity
controller to the cache whose capacity it allocates.

Assisted-by: Claude:claude-opus-4-8
Co-developed-by: Adrien Ricciardi <aricciardi@baylibre.com>
Signed-off-by: Adrien Ricciardi <aricciardi@baylibre.com>
Signed-off-by: Drew Fustini <fustini@kernel.org>
---
 .../devicetree/bindings/riscv/riscv,cbqri.yaml     | 97 ++++++++++++++++++++++
 MAINTAINERS                                        |  1 +
 2 files changed, 98 insertions(+)

diff --git a/Documentation/devicetree/bindings/riscv/riscv,cbqri.yaml b/Documentation/devicetree/bindings/riscv/riscv,cbqri.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..5d6be645381780e187b39e60c3bb487fdf2cfb69
--- /dev/null
+++ b/Documentation/devicetree/bindings/riscv/riscv,cbqri.yaml
@@ -0,0 +1,97 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/riscv/riscv,cbqri.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: RISC-V Capacity and Bandwidth QoS Register Interface (CBQRI) controller
+
+description: |
+  The RISC-V CBQRI specification defines capacity-controller and
+  bandwidth-controller register blocks that allocate cache capacity and memory
+  bandwidth to resource-control IDs (RCIDs) and monitor usage per
+  monitoring-counter ID (MCID):
+  https://github.com/riscv-non-isa/riscv-cbqri/blob/main/riscv-cbqri.pdf
+
+  Allocation and monitoring share one register block, and a controller may
+  implement either or both. A driver discovers which at runtime from the
+  capabilities register, so the compatible names only the controller type. It
+  does not distinguish allocation-only, monitoring-only or combined
+  controllers, and no property declares monitoring support.
+
+maintainers:
+  - Drew Fustini <fustini@kernel.org>
+
+properties:
+  compatible:
+    oneOf:
+      - items:
+          - description: Tenstorrent Ascalon Shared Cache
+            const: tenstorrent,ascalon-sc-cbqri
+          - const: riscv,cbqri-capacity-controller
+      - enum:
+          - riscv,cbqri-capacity-controller
+          - riscv,cbqri-bandwidth-controller
+
+  reg:
+    maxItems: 1
+    description:
+      The CBQRI controller register block.
+
+  riscv,cbqri-rcid:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      The maximum number of RCIDs the controller supports. RCIDs are the
+      resource-control IDs that allocation operations target.
+
+  riscv,cbqri-mcid:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      The maximum number of MCIDs the controller supports. MCIDs are the
+      monitoring-counter IDs that usage-monitoring operations target. Present
+      on controllers that implement monitoring.
+
+  riscv,cbqri-cache:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description:
+      Phandle to the cache node whose capacity this controller allocates.
+      Applies to capacity controllers that back a CPU cache. The cache level
+      and the harts sharing it are taken from that node's cache topology.
+
+required:
+  - compatible
+  - reg
+
+allOf:
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: tenstorrent,ascalon-sc-cbqri
+    then:
+      required:
+        - riscv,cbqri-rcid
+        - riscv,cbqri-cache
+
+additionalProperties: false
+
+examples:
+  - |
+    l2_cache: l2-cache {
+        compatible = "cache";
+        cache-level = <2>;
+        cache-unified;
+        cache-size = <0xc00000>;
+        cache-sets = <512>;
+        cache-block-size = <64>;
+    };
+
+    cache-controller@a21a00c0 {
+        compatible = "tenstorrent,ascalon-sc-cbqri",
+                     "riscv,cbqri-capacity-controller";
+        reg = <0xa21a00c0 0xf40>;
+        riscv,cbqri-rcid = <16>;
+        riscv,cbqri-cache = <&l2_cache>;
+    };
+
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 9e1092165046c773771b055869030bc1bdb64b16..64a95a4d795a57033d3f36200d98cfb4a013ab94 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23298,6 +23298,7 @@ M:	Drew Fustini <fustini@kernel.org>
 R:	yunhui cui <cuiyunhui@bytedance.com>
 L:	linux-riscv@lists.infradead.org
 S:	Supported
+F:	Documentation/devicetree/bindings/riscv/riscv,cbqri.yaml
 F:	arch/riscv/include/asm/qos.h
 F:	arch/riscv/include/asm/resctrl.h
 F:	arch/riscv/kernel/qos.c

-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 8/8] riscv_cbqri: Add CBQRI cache capacity-allocation platform driver
From: Drew Fustini @ 2026-06-25  1:38 UTC (permalink / raw)
  To: 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, yunhui cui, Drew Fustini
  Cc: linux-kernel, linux-riscv, x86, devicetree, linux-rt-devel,
	linux-doc
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-0-2f8049fd902b@kernel.org>

Add a device-tree platform driver, bound to the generic
riscv,cbqri-capacity-controller compatible, that registers a CBQRI
capacity controller as the resctrl cache-allocation resource for the
cache it governs.

The driver follows the node's riscv,cbqri-cache phandle to that cache,
reads its level, and matches it against cacheinfo to get the resctrl
domain id and the harts sharing the cache. It then hands the controller
to riscv_cbqri_register_cc_dt() with the riscv,cbqri-rcid count from the
node.

Nothing is vendor-specific, and the DT "reg" is the CBQRI register block
itself, so any SoC that describes a CBQRI capacity controller in device
tree can reuse the driver unchanged.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Drew Fustini <fustini@kernel.org>
---
 MAINTAINERS                      |   1 +
 drivers/resctrl/Kconfig          |  12 ++++
 drivers/resctrl/Makefile         |   1 +
 drivers/resctrl/cbqri_capacity.c | 132 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 146 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 64a95a4d795a57033d3f36200d98cfb4a013ab94..e0ffccd9ed6ec3c147fb2a4198cbcf6cedd73c9f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23302,6 +23302,7 @@ F:	Documentation/devicetree/bindings/riscv/riscv,cbqri.yaml
 F:	arch/riscv/include/asm/qos.h
 F:	arch/riscv/include/asm/resctrl.h
 F:	arch/riscv/kernel/qos.c
+F:	drivers/resctrl/cbqri_capacity.c
 F:	drivers/resctrl/cbqri_devices.c
 F:	drivers/resctrl/cbqri_internal.h
 F:	drivers/resctrl/cbqri_resctrl.c
diff --git a/drivers/resctrl/Kconfig b/drivers/resctrl/Kconfig
index f8566c003d49570b844908d57c231d73c3bb0f6e..3645dd643117c49a5379dd33fb5ee955f4f8eb3a 100644
--- a/drivers/resctrl/Kconfig
+++ b/drivers/resctrl/Kconfig
@@ -41,6 +41,18 @@ menuconfig RISCV_CBQRI
 
 if RISCV_CBQRI
 
+config RISCV_CBQRI_CAPACITY
+	bool "RISC-V CBQRI cache capacity-allocation controller"
+	depends on OF
+	help
+	  Enable driver for a RISC-V CBQRI capacity controller that
+	  governs a CPU cache, matching the "riscv,cbqri-capacity-controller"
+	  compatible. The controller's cache phandle gives the cache level and the
+	  harts that share it, which the driver registers as a resctrl
+	  cache-allocation resource.
+
+	  Say N unless your device tree describes a CBQRI capacity controller.
+
 endif
 
 config RISCV_CBQRI_RESCTRL_FS
diff --git a/drivers/resctrl/Makefile b/drivers/resctrl/Makefile
index a7631712dba9e1c9dd2a0b07a089204671f85d1f..c8339113ef1f735cd27c4452ae2f73ab348c3230 100644
--- a/drivers/resctrl/Makefile
+++ b/drivers/resctrl/Makefile
@@ -7,3 +7,4 @@ ccflags-$(CONFIG_ARM64_MPAM_DRIVER_DEBUG)	+= -DDEBUG
 obj-$(CONFIG_RISCV_CBQRI)			+= cbqri.o
 cbqri-y						+= cbqri_devices.o
 cbqri-$(CONFIG_RISCV_CBQRI_RESCTRL_FS)		+= cbqri_resctrl.o
+cbqri-$(CONFIG_RISCV_CBQRI_CAPACITY)		+= cbqri_capacity.o
diff --git a/drivers/resctrl/cbqri_capacity.c b/drivers/resctrl/cbqri_capacity.c
new file mode 100644
index 0000000000000000000000000000000000000000..2172432eb3287f5c7db9ab44d0a4dae45c4fa2cc
--- /dev/null
+++ b/drivers/resctrl/cbqri_capacity.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Platform driver for a RISC-V CBQRI capacity controller that backs a CPU
+ * cache. The controller is described in device tree by the generic
+ * "riscv,cbqri-capacity-controller" compatible together with a phandle to the
+ * cache node it governs. The driver hands it to the CBQRI core, which probes
+ * the capabilities register and exposes a controller that supports allocation
+ * as the resctrl cache allocation resource for that cache.
+ */
+
+#define pr_fmt(fmt) "cbqri-capacity: " fmt
+
+#include <linux/cacheinfo.h>
+#include <linux/cpu.h>
+#include <linux/cpumask.h>
+#include <linux/ioport.h>
+#include <linux/mod_devicetable.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/printk.h>
+#include <linux/riscv_cbqri.h>
+#include <linux/types.h>
+
+static int cbqri_capacity_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct cbqri_controller_info info = {};
+	struct device_node *cache_np;
+	cpumask_var_t cpu_mask;
+	struct resource *res;
+	u32 rcid_count, cache_level;
+	int cache_id, cpu, ret;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -EINVAL;
+
+	ret = of_property_read_u32(dev->of_node, "riscv,cbqri-rcid", &rcid_count);
+	if (ret) {
+		dev_err(dev, "missing riscv,cbqri-rcid\n");
+		return ret;
+	}
+
+	cache_np = of_parse_phandle(dev->of_node, "riscv,cbqri-cache", 0);
+	if (!cache_np) {
+		dev_err(dev, "missing riscv,cbqri-cache phandle\n");
+		return -EINVAL;
+	}
+
+	ret = of_property_read_u32(cache_np, "cache-level", &cache_level);
+	if (ret) {
+		dev_err(dev, "%pOF: missing cache-level\n", cache_np);
+		goto out_put;
+	}
+
+	if (!zalloc_cpumask_var(&cpu_mask, GFP_KERNEL)) {
+		ret = -ENOMEM;
+		goto out_put;
+	}
+
+	/*
+	 * Associate the controller with its cache instance via
+	 * cacheinfo. The matching cache provides the cache id and the
+	 * set of harts that share the cache.
+	 */
+	cache_id = -1;
+	cpus_read_lock();
+	for_each_online_cpu(cpu) {
+		struct cacheinfo *ci = get_cpu_cacheinfo_level(cpu, cache_level);
+
+		if (ci && ci->fw_token == cache_np) {
+			cache_id = ci->id;
+			cpumask_copy(cpu_mask, &ci->shared_cpu_map);
+			break;
+		}
+	}
+	cpus_read_unlock();
+
+	if (cache_id < 0) {
+		dev_err(dev, "%pOF: no online hart reports an L%u cache for this node\n",
+			cache_np, cache_level);
+		ret = -ENODEV;
+		goto out_free;
+	}
+
+	info.type = CBQRI_CONTROLLER_TYPE_CAPACITY;
+	info.addr = res->start;
+	info.size = resource_size(res);
+	info.rcid_count = rcid_count;
+	info.cache_id = cache_id;
+
+	ret = riscv_cbqri_register_cc_dt(&info, cache_level, cpu_mask);
+	if (ret) {
+		dev_err(dev, "failed to register capacity controller: %d\n", ret);
+		goto out_free;
+	}
+
+	dev_info(dev, "registered L%u capacity controller at %pa (cache_id=%d, rcid=%u)\n",
+		 cache_level, &info.addr, cache_id, rcid_count);
+
+out_free:
+	free_cpumask_var(cpu_mask);
+out_put:
+	of_node_put(cache_np);
+	return ret;
+}
+
+static const struct of_device_id cbqri_capacity_of_match[] = {
+	{ .compatible = "riscv,cbqri-capacity-controller" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, cbqri_capacity_of_match);
+
+static struct platform_driver cbqri_capacity_driver = {
+	.probe	= cbqri_capacity_probe,
+	.driver = {
+		.name		= "cbqri-capacity",
+		.of_match_table	= cbqri_capacity_of_match,
+		/*
+		 * The controller is registered permanently into the
+		 * CBQRI core for the life of the system. Block unbind
+		 * so userspace cannot leave a dangling controller.
+		 */
+		.suppress_bind_attrs = true,
+	},
+};
+
+/*
+ * Register at device_initcall so probe runs before the CBQRI core's
+ * late_initcall which walks the cbqri_controllers list.
+ */
+builtin_platform_driver(cbqri_capacity_driver);

-- 
2.34.1


^ permalink raw reply related

* Re: [PATCH 0/4] input: misc: Add an initial driver for haptics inside Qcom PMIH010x PMIC
From: Fenglin Wu @ 2026-06-25  1:41 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: linux-arm-msm, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Lee Jones, Stephen Boyd, Bjorn Andersson,
	Konrad Dybcio, David Collins, Subbaraman Narayanamurthy,
	Kamal Wadhwa, kernel, linux-input, devicetree, linux-kernel
In-Reply-To: <4ba2eeed-71f8-4799-b261-e4e2c268043e@kernel.org>


On 6/24/2026 6:05 PM, Krzysztof Kozlowski wrote:
> No. Act as maintainer. Clone Linus tree, apply the patch and see if
> everything works. My claim is that nothing works and maintainer tree is
> broken.
>
> Best regards,
> Krzysztof

Thanks for the explanation. I just did that and I didn't see conflict 
when applying the binding and driver changes, but I did see a conflict 
when applying the DTS change. I will drop the DTS change 1st and resend 
them after the driver and binding changes get accepted.



^ permalink raw reply

* RE: [PATCH v6 2/9] dt-bindings: media: nxp: Add Wave6 video codec device
From: Nas Chung @ 2026-06-25  1:43 UTC (permalink / raw)
  To: Conor Dooley
  Cc: mchehab@kernel.org, hverkuil@xs4all.nl, robh@kernel.org,
	krzk+dt@kernel.org, conor+dt@kernel.org, shawnguo@kernel.org,
	s.hauer@pengutronix.de, linux-media@vger.kernel.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-imx@nxp.com, linux-arm-kernel@lists.infradead.org,
	jackson.lee, lafley.kim, marek.vasut@mailbox.org
In-Reply-To: <20260624-junkyard-sensuous-fcd43189b593@spud>

Hi, Conor.

>-----Original Message-----
>From: Conor Dooley <conor@kernel.org>
>Sent: Thursday, June 25, 2026 1:42 AM
>To: Nas Chung <nas.chung@chipsnmedia.com>
>Cc: mchehab@kernel.org; hverkuil@xs4all.nl; robh@kernel.org;
>krzk+dt@kernel.org; conor+dt@kernel.org; shawnguo@kernel.org;
>s.hauer@pengutronix.de; linux-media@vger.kernel.org;
>devicetree@vger.kernel.org; linux-kernel@vger.kernel.org; linux-imx@nxp.com;
>linux-arm-kernel@lists.infradead.org; jackson.lee
><jackson.lee@chipsnmedia.com>; lafley.kim <lafley.kim@chipsnmedia.com>;
>marek.vasut@mailbox.org
>Subject: Re: [PATCH v6 2/9] dt-bindings: media: nxp: Add Wave6 video codec
>device
>
>On Wed, Jun 24, 2026 at 04:20:36PM +0900, Nas Chung wrote:
>> Add documentation for the Chips&Media Wave6 video codec on NXP i.MX SoCs.
>>
>> The hardware contains one control register region and four interface
>> register regions for a shared video processing engine. The control region
>> manages shared resources such as firmware memory, while each interface
>> region has its own MMIO range and interrupt.
>>
>> The control region and each interface region are distinct DMA requesters
>> and can be associated with separate IOMMU stream IDs. Represent the
>> control region as the parent node and the interface register regions as
>> child nodes to describe these resources.
>>
>> Signed-off-by: Nas Chung <nas.chung@chipsnmedia.com>
>> ---
>>  .../bindings/media/nxp,imx95-vpu.yaml         | 163 ++++++++++++++++++
>>  MAINTAINERS                                   |   7 +
>>  2 files changed, 170 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/media/nxp,imx95-
>vpu.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/media/nxp,imx95-vpu.yaml
>b/Documentation/devicetree/bindings/media/nxp,imx95-vpu.yaml
>> new file mode 100644
>> index 000000000000..9a5ca53e15a3
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/media/nxp,imx95-vpu.yaml
>> @@ -0,0 +1,163 @@
>> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/media/nxp,imx95-vpu.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Chips&Media Wave6 Series multi-standard codec IP on NXP i.MX SoCs
>> +
>> +maintainers:
>> +  - Nas Chung <nas.chung@chipsnmedia.com>
>> +  - Jackson Lee <jackson.lee@chipsnmedia.com>
>> +
>> +description:
>> +  The Chips&Media Wave6 codec IP is a multi-standard video
>encoder/decoder.
>> +  On NXP i.MX SoCs, the Wave6 codec IP exposes one control register
>region and
>> +  four interface register regions for a shared video processing engine.
>> +  The parent node describes the control region, which has its own MMIO
>range and
>> +  manages shared resources such as firmware memory. The child nodes
>describe the
>> +  interface register regions. Each interface region has its own MMIO
>range and
>> +  interrupt.
>> +  The control region and the interface regions are distinct DMA
>requesters.
>> +  The control region and each interface region can be associated with
>separate
>> +  IOMMU stream IDs, allowing DMA isolation between them.
>> +
>> +properties:
>> +  compatible:
>> +    enum:
>> +      - nxp,imx95-vpu
>> +
>> +  reg:
>> +    maxItems: 1
>> +
>> +  clocks:
>> +    items:
>> +      - description: VPU core clock
>> +      - description: VPU associated block clock
>> +
>> +  clock-names:
>> +    items:
>> +      - const: core
>> +      - const: vpublk
>> +
>> +  power-domains:
>> +    items:
>> +      - description: Main VPU power domain
>> +      - description: Performance power domain
>> +
>> +  power-domain-names:
>> +    items:
>> +      - const: vpu
>> +      - const: perf
>> +
>> +  memory-region:
>> +    maxItems: 1
>> +
>> +  sram:
>> +    $ref: /schemas/types.yaml#/definitions/phandle
>> +    description:
>> +      phandle to the SRAM node used to store reference data, reducing DMA
>> +      memory bandwidth.
>> +
>> +  iommus:
>> +    maxItems: 1
>> +
>> +  "#cooling-cells":
>> +    const: 2
>> +
>> +  "#address-cells":
>> +    const: 2
>> +
>> +  "#size-cells":
>> +    const: 2
>> +
>> +  ranges: true
>> +
>> +patternProperties:
>> +  "^interface@[0-9a-f]+$":
>
>I have to wonder if this interface business is required at all.
>Why can this not go into the parent, with each region fetchable via
>reg-names, interrupt-names and iommu-names?

Thanks for your feedback.

I did try the flat model, but the blocker is the IOMMU.

The control region and four interface regions are independent DMA requesters
with distinct stream IDs, and each interface can be assigned to a different VM,
driving the video core with its own isolated memory.

If all stream IDs are listed under the parent's iommus, they bind to a
single device and share one domain, so the isolation is lost.
This is the main reason I added the interface nodes.

Thanks.
Nas.

>
>Cheers,
>Conor.
>
>> +    type: object
>> +    description:
>> +      An interface register region within the Chips&Media Wave6 codec IP.
>> +      Each region has its own MMIO range and interrupt and can be
>associated
>> +      with a separate IOMMU stream ID for DMA isolation.
>> +    additionalProperties: false
>> +
>> +    properties:
>> +      reg:
>> +        maxItems: 1
>> +
>> +      interrupts:
>> +        maxItems: 1
>> +
>> +      iommus:
>> +        maxItems: 1
>> +
>> +    required:
>> +      - reg
>> +      - interrupts
>> +
>> +required:
>> +  - compatible
>> +  - reg
>> +  - clocks
>> +  - clock-names
>> +  - power-domains
>> +  - power-domain-names
>> +  - memory-region
>> +  - "#address-cells"
>> +  - "#size-cells"
>> +  - ranges
>> +
>> +additionalProperties: false
>> +
>> +examples:
>> +  - |
>> +    #include <dt-bindings/interrupt-controller/arm-gic.h>
>> +    #include <dt-bindings/clock/nxp,imx95-clock.h>
>> +
>> +    soc {
>> +      #address-cells = <2>;
>> +      #size-cells = <2>;
>> +
>> +      video-codec@4c4c0000 {
>> +        compatible = "nxp,imx95-vpu";
>> +        reg = <0x0 0x4c4c0000 0x0 0x10000>;
>> +        clocks = <&scmi_clk 115>,
>> +                 <&vpu_blk_ctrl IMX95_CLK_VPUBLK_WAVE>;
>> +        clock-names = "core", "vpublk";
>> +        power-domains = <&scmi_devpd 21>,
>> +                        <&scmi_perf 10>;
>> +        power-domain-names = "vpu", "perf";
>> +        memory-region = <&vpu_boot>;
>> +        sram = <&sram1>;
>> +        iommus = <&smmu 0x32>;
>> +        #cooling-cells = <2>;
>> +        #address-cells = <2>;
>> +        #size-cells = <2>;
>> +        ranges;
>> +
>> +        interface@4c480000 {
>> +          reg = <0x0 0x4c480000 0x0 0x10000>;
>> +          interrupts = <GIC_SPI 299 IRQ_TYPE_LEVEL_HIGH>;
>> +          iommus = <&smmu 0x33>;
>> +        };
>> +
>> +        interface@4c490000 {
>> +          reg = <0x0 0x4c490000 0x0 0x10000>;
>> +          interrupts = <GIC_SPI 300 IRQ_TYPE_LEVEL_HIGH>;
>> +          iommus = <&smmu 0x34>;
>> +        };
>> +
>> +        interface@4c4a0000 {
>> +          reg = <0x0 0x4c4a0000 0x0 0x10000>;
>> +          interrupts = <GIC_SPI 301 IRQ_TYPE_LEVEL_HIGH>;
>> +          iommus = <&smmu 0x35>;
>> +        };
>> +
>> +        interface@4c4b0000 {
>> +          reg = <0x0 0x4c4b0000 0x0 0x10000>;
>> +          interrupts = <GIC_SPI 302 IRQ_TYPE_LEVEL_HIGH>;
>> +          iommus = <&smmu 0x36>;
>> +        };
>> +      };
>> +    };
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index efbf808063e5..77ea3a1a966b 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -28688,6 +28688,13 @@ S:	Maintained
>>  F:	Documentation/devicetree/bindings/media/cnm,wave521c.yaml
>>  F:	drivers/media/platform/chips-media/wave5/
>>
>> +WAVE6 VPU CODEC DRIVER
>> +M:	Nas Chung <nas.chung@chipsnmedia.com>
>> +M:	Jackson Lee <jackson.lee@chipsnmedia.com>
>> +L:	linux-media@vger.kernel.org
>> +S:	Maintained
>> +F:	Documentation/devicetree/bindings/media/nxp,imx95-vpu.yaml
>> +
>>  WHISKEYCOVE PMIC GPIO DRIVER
>>  M:	Kuppuswamy Sathyanarayanan
><sathyanarayanan.kuppuswamy@linux.intel.com>
>>  L:	linux-gpio@vger.kernel.org
>> --
>> 2.31.1
>>

^ permalink raw reply

* Re: [PATCH v2 3/8] riscv: Add support for srmcfg CSR from Ssqosid extension
From: sashiko-bot @ 2026-06-25  1:50 UTC (permalink / raw)
  To: Drew Fustini; +Cc: robh, linux-kernel, conor+dt, devicetree, linux-rt-devel
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-3-2f8049fd902b@kernel.org>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] CPU hotplug cache invalidation is registered at the wrong state (CPUHP_AP_ONLINE_DYN), allowing the srmcfg cache sentinel to be overwritten before the CPU goes offline.
--

commit d51eb8c4a6c5a57f238cc53688229c08ea6418e1
Author: Drew Fustini <fustini@kernel.org>

riscv: Add support for srmcfg CSR from Ssqosid extension

This commit adds support for the srmcfg CSR defined in the Ssqosid ISA
extension. It introduces a per-cpu cache to avoid redundant CSR writes
during context switches and uses CPU hotplug and PM notifiers to manage
cache invalidation.

Link: https://github.com/riscv/riscv-ssqosid/releases/tag/v1.0

> diff --git a/arch/riscv/kernel/qos.c b/arch/riscv/kernel/qos.c
> new file mode 100644
> index 0000000000000..ea33201a43f61
> --- /dev/null
> +++ b/arch/riscv/kernel/qos.c

[ ... ]

> +static int riscv_srmcfg_reset_cache(unsigned int cpu)
> +{
> +	per_cpu(cpu_srmcfg, cpu) = U32_MAX;
> +	return 0;
> +}

[ ... ]

> +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);

[Severity: High]
Does registering the hotplug callbacks at CPUHP_AP_ONLINE_DYN run too early
during teardown and too late during startup?

Since CPUHP_AP_ONLINE_DYN teardown runs while the CPU is still schedulable,
could the U32_MAX sentinel set by riscv_srmcfg_reset_cache() be overwritten
by subsequent context switches (such as switching to the idle thread) before
the CPU actually goes offline?

If a CPU is offlined and then onlined, the stale cache would persist while
the hardware CSR has been reset. Because the CPU becomes schedulable before
the CPUHP_AP_ONLINE_DYN startup callback runs, a task matching the stale
cache might skip the required CSR write and run with incorrect hardware tags.

> +	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;
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260624-dfustini-atl-sc-cbqri-dt-v2-0-2f8049fd902b@kernel.org?part=3

^ permalink raw reply

* Re: [PATCH v2 5/8] riscv_cbqri: resctrl: Add cache allocation via capacity block mask
From: sashiko-bot @ 2026-06-25  1:53 UTC (permalink / raw)
  To: Drew Fustini; +Cc: conor+dt, devicetree, linux-kernel, robh, linux-rt-devel
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-5-2f8049fd902b@kernel.org>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Traversing the global `cbqri_controllers` list without holding `cbqri_controllers_lock` during CPU hotplug risks list corruption.
--

commit e1c49060a1fc98c61e36689ccda2041d360bb762
Author: Drew Fustini <fustini@kernel.org>

riscv_cbqri: resctrl: Add cache allocation via capacity block mask

This commit wires up the CBQRI capacity controllers into resctrl as
RDT_RESOURCE_L2 and RDT_RESOURCE_L3 schemata. It treats mismatched
capabilities at the same cache level as a fatal error and lazily creates
domains in the cpuhp online callback.

> diff --git a/drivers/resctrl/cbqri_resctrl.c b/drivers/resctrl/cbqri_resctrl.c
> new file mode 100644
> index 0000000000000..2f31baff032b6
> --- /dev/null
> +++ b/drivers/resctrl/cbqri_resctrl.c
[ ... ]
> +static int cbqri_attach_cpu_to_all_ctrls(unsigned int cpu)
> +{
> +	struct cbqri_controller *ctrl;
> +	int err = 0;
> +
> +	lockdep_assert_held(&cbqri_domain_list_lock);
> +
> +	list_for_each_entry(ctrl, &cbqri_controllers, list) {

[Severity: High]
Is it safe to iterate the global cbqri_controllers list without holding
cbqri_controllers_lock?

If a capacity controller is dynamically added at runtime (e.g., via a device
tree overlay instantiating the platform device), its probe function can acquire
cbqri_controllers_lock and mutate the cbqri_controllers list.

Concurrently, if a CPU is hotplugged, the cpuhp online callback calls
cbqri_resctrl_online_cpu() which invokes this function. Traversing the list
here while holding only cbqri_domain_list_lock could lead to list corruption,
resulting in an infinite loop or NULL pointer dereference.

> +		if (ctrl->type != CBQRI_CONTROLLER_TYPE_CAPACITY)
> +			continue;
> +		if (!cpumask_test_cpu(cpu, &ctrl->cache.cpu_mask))
> +			continue;
> +		if (!ctrl->alloc_capable)
> +			continue;
> +
> +		err = cbqri_attach_cpu_to_cap_ctrl(ctrl, cpu);
> +		if (err) {
> +			cbqri_detach_cpu_from_all_ctrls(cpu);
> +			break;
> +		}
> +	}
> +
> +	return err;
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260624-dfustini-atl-sc-cbqri-dt-v2-0-2f8049fd902b@kernel.org?part=5

^ permalink raw reply

* [PATCH v2 2/3] dt-bindings: mfd: qcom,spmi-pmic: Document haptics device
From: Fenglin Wu @ 2026-06-25  2:00 UTC (permalink / raw)
  To: linux-arm-msm, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Lee Jones, Stephen Boyd, Bjorn Andersson,
	Konrad Dybcio
  Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa, kernel,
	linux-input, devicetree, linux-kernel, Fenglin Wu
In-Reply-To: <20260624-qcom-spmi-haptics-v2-0-b9118e60f3e3@oss.qualcomm.com>

Some of the Qualcomm SPMI PMIC has haptics device in it, add it in the
device list.

Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com>
---
 Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml
index 644c42b5e2e5..773f4cba5935 100644
--- a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml
+++ b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml
@@ -165,6 +165,10 @@ patternProperties:
     type: object
     $ref: /schemas/pinctrl/qcom,pmic-gpio.yaml#
 
+  "^haptics@[0-9a-f]+$":
+    type: object
+    $ref: /schemas/input/qcom,spmi-haptics.yaml#
+
   "^led-controller@[0-9a-f]+$":
     type: object
     $ref: /schemas/leds/qcom,spmi-flash-led.yaml#

-- 
2.43.0


^ permalink raw reply related

* [PATCH v2 0/3] input: misc: Add an initial driver for haptics inside Qcom PMIH010x PMIC
From: Fenglin Wu @ 2026-06-25  2:00 UTC (permalink / raw)
  To: linux-arm-msm, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Lee Jones, Stephen Boyd, Bjorn Andersson,
	Konrad Dybcio
  Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa, kernel,
	linux-input, devicetree, linux-kernel, Fenglin Wu

Qualcomm PMIH0108 PMIC has a haptics module inside and it could drive
a LRA actuator with several play modes, including: DIRECT_PLAY, FIFO,
PAT_MEM, SWR, etc. Add an initial driver to support two of the play
modes using the input force-feedback framework:

-- FF_CONSTANT effect for DIRECT_PLAY mode which drives sinusoidual
  waveforms with fixed period and amplitude, which would generate
  a constant vibration effect on the LRA actuator.

-- FF_PERIODIC effect with FF_CUSTOM for FIFO streaming mode, which
  can play an arbitrary waveform composed of a sequence of 8-bit
  samples at a configurable play rate.

Also, add the device node in the existing pmih0108 dtsi files, and enble
the haptics device for several boards by updating the vmax and
lra-period sttings according to the LRA components that mounted on each
of them.

Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com>
---
Changes in v2:

Dropped dtsi change and I will resend them after the driver and binding changes get accepted.

Updated haptics binding and addressed review comments from Krzysztof and Konrad:
- Extended the description to clarify the 'PAT_MEM' mode (not yet supported in the driver)
  by comparing it with the 'FIFO' mode.
- Updated the compatible string to 'qcom,spmi-haptics' to match the file name and removed
  the PMIC wildcard.
- Simplified register names to 'cfg' and 'ptn'.
- Corrected the unit naming for the 'qcom,vmax-microvolt' property.
- Added an additional clarification for the 'qcom,lra-period-us' property.

Updated the driver to address review comments from Konrad and Julian:
- In haptics_write_fifo_chunk(), separated variable declaration and assignment, and added
  comments explaining the 4-byte and 1-byte FIFO writes.
- Replaced manual 'x * n / d' calculations with mult_frac().
- Switched to disable_irq() to prevent late IRQs after device removal.
- Replaced property reads with device_property_read_u32().
- Remove the 'INPUT' dependency in Kconfig

Updated the driver to address feedback from Sashiko AI:
- Guarded pm_runtime_resume()/suspend() with 'pm_ref_held' to prevent runtime PM reference leaks.
- Replaced spinlock with a mutex to protect FIFO data during playback and avoid calling
  sleepable regmap APIs under spinlock.
- Adjusted suspend/remove() sequence to stop playback before canceling work, and freed
  FIFO buffers to prevent potential memory leaks.
- In FF_PERIODIC handling, allocated 'fifo_data' before assigning data to ensure its
  consistency with 'data_len'.
- Registered the input device after enabling runtime PM.
- Unify to use 'h->dev' pointer in probe()

- Link to v1: https://patch.msgid.link/20260616-qcom-spmi-haptics-v1-0-d24e422de6b4@oss.qualcomm.com

---
Fenglin Wu (3):
      dt-bindings: input: Add Qualcomm SPMI PMIC haptics
      dt-bindings: mfd: qcom,spmi-pmic: Document haptics device
      input: misc: Add Qualcomm SPMI PMIC haptics driver

 .../bindings/input/qcom,spmi-haptics.yaml          | 132 ++++
 .../devicetree/bindings/mfd/qcom,spmi-pmic.yaml    |   4 +
 drivers/input/misc/Kconfig                         |  11 +
 drivers/input/misc/Makefile                        |   1 +
 drivers/input/misc/qcom-spmi-haptics.c             | 838 +++++++++++++++++++++
 5 files changed, 986 insertions(+)
---
base-commit: 66725039f7090afe14c31bd259e2059a68f04023
change-id: 20260616-qcom-spmi-haptics-3cc97e7b232e

Best regards,
--  
Fenglin Wu <fenglin.wu@oss.qualcomm.com>


^ permalink raw reply

* [PATCH v2 1/3] dt-bindings: input: Add Qualcomm SPMI PMIC haptics
From: Fenglin Wu @ 2026-06-25  2:00 UTC (permalink / raw)
  To: linux-arm-msm, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Lee Jones, Stephen Boyd, Bjorn Andersson,
	Konrad Dybcio
  Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa, kernel,
	linux-input, devicetree, linux-kernel, Fenglin Wu
In-Reply-To: <20260624-qcom-spmi-haptics-v2-0-b9118e60f3e3@oss.qualcomm.com>

Add binding document for the haptics module inside Qualcomm PMIC
PMIH0108.

Assisted-by: Claude:claude-4-6-sonnet
Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com>
---
 .../bindings/input/qcom,spmi-haptics.yaml          | 132 +++++++++++++++++++++
 1 file changed, 132 insertions(+)

diff --git a/Documentation/devicetree/bindings/input/qcom,spmi-haptics.yaml b/Documentation/devicetree/bindings/input/qcom,spmi-haptics.yaml
new file mode 100644
index 000000000000..3764c3e113a3
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/qcom,spmi-haptics.yaml
@@ -0,0 +1,132 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/qcom,spmi-haptics.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Haptics device inside Qualcomm Technologies, Inc. PMIC
+
+maintainers:
+  - Fenglin Wu <fenglin.wu@oss.qualcomm.com>
+
+description: |
+  Certain Qualcomm PMICs integrate a haptics module, such as the HAP530_HV haptics
+  module in the PMIH0108 PMIC, which drives an LRA (Linear Resonant Actuator) with
+  an output voltage up to 10 V. Several play modes are supported in HAP530_HV:
+
+    DIRECT_PLAY: The hardware outputs sinusoidal waveforms whose period is
+      defined by lra-period-us and whose peak voltage is defined by vmax-microvolt.
+      The driving amplitude can be scaled in the range [0, 255] via a single
+      register byte.  Hardware-based LRA auto-resonance tracking is enabled by
+      default in this mode, allowing the haptics engine to follow the actual
+      resonant frequency of the LRA and update the driving period accordingly
+      to achieve stronger vibration magnitude.
+
+    FIFO: The hardware can play an arbitrary waveform composed of a sequence
+      of 8-bit samples at a configurable play rate.  Samples are pre-filled
+      into the internal FIFO memory of the haptics module and continuously
+      replenished via the FIFO-empty IRQ until all samples have been played.
+      An 8K-byte FIFO memory bank is available in the HAP530_HV haptics module,
+      shared between the FIFO and PAT_MEM play modes. The memory partition
+      between the two modes is configurable via registers, and FIFO mode always
+      uses the 1st partition starting from offset 0.
+
+    PAT_MEM: This mode is very similar to FIFO streaming mode but without the
+      data refilling capability. It is designed mainly for short, latency-critical
+      vibrations. The memory space for PAT_MEM mode must be reserved for dedicated
+      usage, and the waveform data should be preloaded and remain unchanged
+      thereafter. The haptics module can play the waveform data from the memory
+      region specified by the PAT_MEM play start address and length registers.
+
+    In either FIFO mode or PAT_MEM mode, the following play rates are supported:
+      -- 0(T_LRA): each FIFO byte drives one full sinusoidal cycle with the
+        period defined in lra-period-us.
+      -- 1/2/3(T_LRA_DIV_2/4/8): each FIFO byte drives a half/quarter/eighth
+        sinusoidal cycle with the period defined in lra-period-us.
+      -- 8/9/10/11/12/13(8KHz/16KHz/24KHz/32KHz/44.1KHz/48KHz): the FIFO
+        data is treated as PCM samples and drives the output with an
+        arbitrarily shaped waveform.  This mode is typically used to define
+        custom driving waveforms for specific vibration effects such as fast
+        attack, crisp brake, etc.
+
+    The drive voltage in FIFO or PAT_MEM mode can exceed the value defined in
+    vmax-mv to achieve a special vibration effect, but the waveform must be
+    short enough to prevent the LRA from being damaged by operating at an
+    overvoltage.
+
+    Also, hardware-based LRA auto-resonance tracking is normally disabled in
+    FIFO or PAT_MEM mode, as these modes are intended to drive arbitrary
+    waveforms that may not follow the resonant frequency; autonomous hardware
+    resonance correction would interfere with the intended output.
+
+properties:
+  compatible:
+    const: qcom,spmi-haptics
+
+  reg:
+    items:
+      - description: HAP_CFG module base address
+      - description: HAP_PTN module base address
+
+  reg-names:
+    items:
+      - const: cfg
+      - const: ptn
+
+  interrupts:
+    maxItems: 1
+
+  interrupt-names:
+    items:
+      - const: fifo-empty
+
+  qcom,vmax-microvolt:
+    description:
+      Maximum allowed output driving voltage in microvolts, rounded to the
+      nearest 50,000 uV step. This is the peak driving voltage in DIRECT_PLAY
+      mode, which outputs sinusoidal waveforms. The value should be equal to
+      the square root of 2 times the Vrms voltage of the LRA.
+    $ref: /schemas/types.yaml#/definitions/uint32
+    minimum: 50000
+    maximum: 10000000
+    multipleOf: 50000
+
+  qcom,lra-period-us:
+    description:
+      LRA actuator initial resonance period in microseconds
+      (1,000,000 / resonant_freq_hz).  Used to configure T_LRA-based play
+      rates and the auto-resonance zero-crossing window. It could be also used
+      as the initial period if the LRA wants to be driven off resonance.
+    minimum: 5
+    maximum: 20475
+
+required:
+  - compatible
+  - reg
+  - reg-names
+  - interrupts
+  - interrupt-names
+  - qcom,vmax-microvolt
+  - qcom,lra-period-us
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    pmic {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        haptics@f000 {
+            compatible = "qcom,spmi-haptics";
+            reg = <0xf000>, <0xf100>;
+            reg-names = "cfg", "ptn";
+            interrupts = <0x7 0xf0 0x1 IRQ_TYPE_EDGE_RISING>;
+            interrupt-names = "fifo-empty";
+
+            qcom,vmax-microvolt = <1300000>;
+            qcom,lra-period-us = <5880>;
+        };
+    };

-- 
2.43.0


^ permalink raw reply related

* [PATCH v2 3/3] input: misc: Add Qualcomm SPMI PMIC haptics driver
From: Fenglin Wu @ 2026-06-25  2:00 UTC (permalink / raw)
  To: linux-arm-msm, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Lee Jones, Stephen Boyd, Bjorn Andersson,
	Konrad Dybcio
  Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa, kernel,
	linux-input, devicetree, linux-kernel, Fenglin Wu
In-Reply-To: <20260624-qcom-spmi-haptics-v2-0-b9118e60f3e3@oss.qualcomm.com>

Add an initial driver for the Qualcomm PMIH0108 PMIC haptics module,
named as HAP530_HV. This module supports several play modes, including
DIRECT_PLAY, FIFO, PAT_MEM, and SWR, each with distinct data sourcing
and hardware data handling logic. Currently, the driver provides support
for two play modes using the input force-feedback framework: FF_CONSTANT
effect for DIRECT_PLAY mode and FF_PERIODIC effect with FF_CUSTOM
waveform for FIFO mode.

Assisted-by: Claude:claude-4-6-sonnet
Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com>
---
 drivers/input/misc/Kconfig             |  11 +
 drivers/input/misc/Makefile            |   1 +
 drivers/input/misc/qcom-spmi-haptics.c | 838 +++++++++++++++++++++++++++++++++
 3 files changed, 850 insertions(+)

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 1f6c57dba030..4f40940973e4 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -236,6 +236,17 @@ config INPUT_PMIC8XXX_PWRKEY
 	  To compile this driver as a module, choose M here: the
 	  module will be called pmic8xxx-pwrkey.
 
+config INPUT_QCOM_SPMI_HAPTICS
+	tristate "Qualcomm SPMI PMIC haptics support"
+	depends on MFD_SPMI_PMIC
+	help
+	  Say Y to enable support for the Qualcomm PMIH0108 SPMI PMIC haptics
+	  module. Supports DIRECT_PLAY, FIFO streaming play modes via the
+	  Linux input force-feedback framework.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called qcom-spmi-haptics.
+
 config INPUT_SPARCSPKR
 	tristate "SPARC Speaker support"
 	depends on PCI && SPARC64
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 2281d6803fce..c5c9aa139a11 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -69,6 +69,7 @@ obj-$(CONFIG_INPUT_PMIC8XXX_PWRKEY)	+= pmic8xxx-pwrkey.o
 obj-$(CONFIG_INPUT_POWERMATE)		+= powermate.o
 obj-$(CONFIG_INPUT_PWM_BEEPER)		+= pwm-beeper.o
 obj-$(CONFIG_INPUT_PWM_VIBRA)		+= pwm-vibra.o
+obj-$(CONFIG_INPUT_QCOM_SPMI_HAPTICS)	+= qcom-spmi-haptics.o
 obj-$(CONFIG_INPUT_QNAP_MCU)		+= qnap-mcu-input.o
 obj-$(CONFIG_INPUT_RAVE_SP_PWRBUTTON)	+= rave-sp-pwrbutton.o
 obj-$(CONFIG_INPUT_RB532_BUTTON)	+= rb532_button.o
diff --git a/drivers/input/misc/qcom-spmi-haptics.c b/drivers/input/misc/qcom-spmi-haptics.c
new file mode 100644
index 000000000000..4b27638df960
--- /dev/null
+++ b/drivers/input/misc/qcom-spmi-haptics.c
@@ -0,0 +1,838 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/pm_runtime.h>
+#include <linux/uaccess.h>
+#include <linux/workqueue.h>
+
+/* HAP_CFG register offsets, bit fields, value constants */
+#define HAP_CFG_INT_RT_STS_REG		0x10
+#define  FIFO_EMPTY_BIT			BIT(1)
+#define HAP_CFG_EN_CTL_REG		0x46
+#define  HAPTICS_EN_BIT			BIT(7)
+#define HAP_CFG_VMAX_REG		0x48
+#define   VMAX_STEP_MV			50
+#define   VMAX_MV_MAX			10000
+#define HAP_CFG_SPMI_PLAY_REG		0x4C
+#define  PLAY_EN_BIT			BIT(7)
+#define  PAT_SRC_MASK			GENMASK(2, 0)
+#define   PAT_SRC_FIFO			0
+#define   PAT_SRC_DIRECT_PLAY		1
+#define HAP_CFG_TLRA_OL_HIGH_REG	0x5C
+#define  TLRA_OL_MSB_MASK		GENMASK(3, 0)
+#define   TLRA_STEP_US			5
+#define HAP_CFG_TLRA_OL_LOW_REG		0x5D
+#define HAP_CFG_DRV_DUTY_CFG_REG	0x60
+#define  ADT_DRV_DUTY_EN_BIT		BIT(7)
+#define  ADT_BRK_DUTY_EN_BIT		BIT(6)
+#define  DRV_DUTY_MASK			GENMASK(5, 3)
+#define   AUTORES_DRV_DUTY_62P5		2
+#define  BRK_DUTY_MASK			GENMASK(2, 0)
+#define   AUTORES_BRK_DUTY_62P5		5
+#define HAP_CFG_ZX_WIND_CFG_REG		0x62
+#define  ZX_DEBOUNCE_MASK		GENMASK(6, 4)
+#define   AUTORES_ZX_DEBOUNCE		3
+#define  ZX_WIN_HEIGHT_MASK		GENMASK(2, 0)
+#define   AUTORES_ZX_WIN_HEIGHT		2
+#define HAP_CFG_AUTORES_CFG_REG		0x63
+#define  AUTORES_EN_BIT			BIT(7)
+#define  AUTORES_EN_DLY_MASK		GENMASK(6, 2)
+#define   AUTORES_EN_DLY_CYCLES		10
+#define  AUTORES_ERR_WIN_MASK		GENMASK(1, 0)
+#define   AUTORES_ERR_WIN_25PCT		1
+#define HAP_CFG_FAULT_CLR_REG		0x66
+#define  ZX_TO_FAULT_CLR_BIT		BIT(4)
+#define  SC_CLR_BIT			BIT(2)
+#define  AUTO_RES_ERR_CLR_BIT		BIT(1)
+#define  HPWR_RDY_FAULT_CLR_BIT		BIT(0)
+#define  FAULT_CLR_ALL	(ZX_TO_FAULT_CLR_BIT | SC_CLR_BIT | \
+			 AUTO_RES_ERR_CLR_BIT | HPWR_RDY_FAULT_CLR_BIT)
+#define HAP_CFG_RAMP_DN_CFG2_REG	0x86
+#define  AUTORES_PRE_HIZ_DLY_10US	1
+
+/* HAP_PTN register offsets, bit fields, value constants */
+#define HAP_PTN_REVISION2_REG		0x01
+#define HAP_PTN_FIFO_DIN_0_REG		0x20
+#define HAP_PTN_FIFO_PLAY_RATE_REG	0x24
+#define  FIFO_PLAY_RATE_MASK		GENMASK(3, 0)
+#define HAP_PTN_DIRECT_PLAY_REG		0x26
+#define HAP_PTN_FIFO_EMPTY_CFG_REG	0x2A
+#define  FIFO_THRESH_LSB		64
+#define HAP_PTN_FIFO_DIN_1B_REG		0x2C
+#define HAP_PTN_MEM_OP_ACCESS_REG	0x2D
+#define  MEM_FLUSH_RELOAD_BIT		BIT(0)
+#define HAP_PTN_MMAP_FIFO_REG		0xA0
+#define  MMAP_FIFO_EXIST_BIT		BIT(7)
+#define  MMAP_FIFO_LEN_MASK		GENMASK(6, 0)
+#define HAP_PTN_PATX_PLAY_CFG_REG	0xA2
+
+#define HAP530_MEM_TOTAL_BYTES		8192
+#define FIFO_EMPTY_THRESH		280
+#define FIFO_INIT_FILL			320
+
+#define HAPTICS_AUTOSUSPEND_MS		1000
+
+/*
+ * FF_CUSTOM data layout (custom_data[] of type s16):
+ *   [0] = play rate (PLAY_RATE_*)
+ *   [1] = vmax in mV (0 = use device default from qcom,vmax-microvolt)
+ *   [2..N-1] = signed 8-bit PCM samples packed one per s16 (lower byte used)
+ */
+#define CUSTOM_DATA_RATE_IDX		0
+#define CUSTOM_DATA_VMAX_IDX		1
+#define CUSTOM_DATA_SAMPLE_START	2
+
+#define HAPTICS_MAX_EFFECTS		8
+
+enum qcom_haptics_mode {
+	HAPTICS_DIRECT_PLAY,
+	HAPTICS_FIFO,
+};
+
+enum qcom_haptics_play_rate {
+	PLAY_RATE_T_LRA       = 0,
+	PLAY_RATE_T_LRA_DIV_2 = 1,
+	PLAY_RATE_T_LRA_DIV_4 = 2,
+	PLAY_RATE_T_LRA_DIV_8 = 3,
+	/* 4–7 are reserved */
+	PLAY_RATE_F_8KHZ      = 8,
+	PLAY_RATE_F_16KHZ     = 9,
+	PLAY_RATE_F_24KHZ     = 10,
+	PLAY_RATE_F_32KHZ     = 11,
+	PLAY_RATE_F_44P1KHZ   = 12,
+	PLAY_RATE_F_48KHZ     = 13,
+	PLAY_RATE_MAX	      = PLAY_RATE_F_48KHZ,
+};
+
+struct qcom_haptics_effect {
+	enum qcom_haptics_mode mode;
+	enum qcom_haptics_play_rate play_rate;
+	u32 vmax_mv;
+	s8 *fifo_data;
+	u32 data_len;
+};
+
+/**
+ * struct qcom_haptics
+ * @dev:          underlying SPMI device
+ * @regmap:       regmap for SPMI register access
+ * @input:        input device exposing the FF interface
+ * @cfg_base:     base address of the CFG peripheral
+ * @ptn_base:     base address of the PTN peripheral
+ * @t_lra_us:     LRA resonance period in microseconds
+ * @vmax_mv:      maximum actuator drive voltage in millivolts
+ * @fifo_len:     programmed HW FIFO depth in bytes
+ * @gain:         playback gain scaler
+ * @play_work:    deferred work item that starts or stops playback
+ * @play_lock:    mutex lock to serialize playbacks
+ * @cur_effect_id: index into @effects[] identifying the active effect
+ * @fifo_empty_irq: IRQ number for the FIFO-empty interrupt
+ * @play_request: true when a playback is requested
+ * @pm_ref_held:  true while a pm_runtime_get is held
+ * @irq_enabled:  true if fifo_empty_irq is enabled
+ * @fifo_lock:    mutex protecting the FIFO streaming data
+ * @fifo_data:    pointer of the data buffer for FIFO streaming
+ * @data_len:     length of the data buffer for current effect
+ * @data_written: number of samples written to the hardware FIFO
+ * @data_done:    flag to indicate that all samples have been written
+ * @effects:      table of the effects
+ */
+struct qcom_haptics {
+	struct device *dev;
+	struct regmap *regmap;
+	struct input_dev *input;
+
+	u32 cfg_base;
+	u32 ptn_base;
+	u32 t_lra_us;
+	u32 vmax_mv;
+	u32 fifo_len;
+	u16 gain;
+
+	struct work_struct play_work;
+	struct mutex play_lock; /* mutex used to serialize playbacks */
+	int cur_effect_id;
+	int fifo_empty_irq;
+	bool play_request;
+	bool pm_ref_held;
+	bool irq_enabled;
+
+	struct mutex fifo_lock; /* protect the FIFO data during play */
+	const s8 *fifo_data;
+	u32 data_len;
+	u32 data_written;
+	bool data_done;
+
+	struct qcom_haptics_effect effects[HAPTICS_MAX_EFFECTS];
+};
+
+static int cfg_write(struct qcom_haptics *h, u32 off, u32 val)
+{
+	return regmap_write(h->regmap, h->cfg_base + off, val);
+}
+
+static int cfg_update_bits(struct qcom_haptics *h, u32 off, u32 mask, u32 val)
+{
+	return regmap_update_bits(h->regmap, h->cfg_base + off, mask, val);
+}
+
+static int ptn_write(struct qcom_haptics *h, u32 off, u32 val)
+{
+	return regmap_write(h->regmap, h->ptn_base + off, val);
+}
+
+static int ptn_update_bits(struct qcom_haptics *h, u32 off, u32 mask, u32 val)
+{
+	return regmap_update_bits(h->regmap, h->ptn_base + off, mask, val);
+}
+
+static int ptn_bulk_write(struct qcom_haptics *h, u32 off,
+			  const void *buf, size_t count)
+{
+	return regmap_bulk_write(h->regmap, h->ptn_base + off, buf, count);
+}
+
+static int haptics_clear_faults(struct qcom_haptics *h)
+{
+	return cfg_write(h, HAP_CFG_FAULT_CLR_REG, FAULT_CLR_ALL);
+}
+
+static int haptics_set_vmax(struct qcom_haptics *h, u32 vmax_mv)
+{
+	return cfg_write(h, HAP_CFG_VMAX_REG, vmax_mv / VMAX_STEP_MV);
+}
+
+static int haptics_config_lra_period(struct qcom_haptics *h)
+{
+	u32 tmp = h->t_lra_us / TLRA_STEP_US;
+	int ret;
+
+	ret = cfg_write(h, HAP_CFG_TLRA_OL_HIGH_REG, (tmp >> 8) & TLRA_OL_MSB_MASK);
+	if (ret)
+		return ret;
+
+	return cfg_write(h, HAP_CFG_TLRA_OL_LOW_REG, tmp & 0xFF);
+}
+
+static int haptics_enable_module(struct qcom_haptics *h, bool enable)
+{
+	return cfg_update_bits(h, HAP_CFG_EN_CTL_REG, HAPTICS_EN_BIT,
+			       enable ? HAPTICS_EN_BIT : 0);
+}
+
+static int haptics_configure_autores(struct qcom_haptics *h)
+{
+	int ret;
+
+	/* AUTORES_CFG: enable, 10-cycle delay, 25% error window */
+	ret = cfg_write(h, HAP_CFG_AUTORES_CFG_REG,
+			AUTORES_EN_BIT |
+			FIELD_PREP(AUTORES_EN_DLY_MASK, AUTORES_EN_DLY_CYCLES) |
+			FIELD_PREP(AUTORES_ERR_WIN_MASK, AUTORES_ERR_WIN_25PCT));
+	if (ret)
+		return ret;
+
+	/* DRV_DUTY: adaptive drive/brake duty cycles at 62.5% */
+	ret = cfg_write(h, HAP_CFG_DRV_DUTY_CFG_REG,
+			ADT_DRV_DUTY_EN_BIT | ADT_BRK_DUTY_EN_BIT |
+			FIELD_PREP(DRV_DUTY_MASK, AUTORES_DRV_DUTY_62P5) |
+			FIELD_PREP(BRK_DUTY_MASK, AUTORES_BRK_DUTY_62P5));
+	if (ret)
+		return ret;
+
+	/* Pre-HIZ delay: 10 µs */
+	ret = cfg_write(h, HAP_CFG_RAMP_DN_CFG2_REG, AUTORES_PRE_HIZ_DLY_10US);
+	if (ret)
+		return ret;
+
+	/* Zero-cross window: debounce 3, no hysteresis, height 2 */
+	return cfg_write(h, HAP_CFG_ZX_WIND_CFG_REG,
+			 FIELD_PREP(ZX_DEBOUNCE_MASK, AUTORES_ZX_DEBOUNCE) |
+			 FIELD_PREP(ZX_WIN_HEIGHT_MASK, AUTORES_ZX_WIN_HEIGHT));
+}
+
+static int haptics_write_fifo_chunk(struct qcom_haptics *h,
+				    const s8 *data, u32 len)
+{
+	u32 bulk_len = ALIGN_DOWN(len, 4);
+	int i, ret;
+
+	/*
+	 * FIFO data writing supports both 4-byte bulk writes using registers
+	 * [HAP_PTN_FIFO_DIN_0_REG ... HAP_PTN_FIFO_DIN_3_REG], and 1-byte writes
+	 * using the HAP_PTN_FIFO_DIN_1B_REG register. The 4-byte bulk write is more
+	 * efficient, so use 4-byte writes for the initial 4-byte aligned data,
+	 * and 1-byte writes for any trailing remainder.
+	 */
+	for (i = 0; i < bulk_len; i += 4) {
+		ret = ptn_bulk_write(h, HAP_PTN_FIFO_DIN_0_REG, &data[i], 4);
+		if (ret)
+			return ret;
+	}
+
+	for (; i < len; i++) {
+		ret = ptn_write(h, HAP_PTN_FIFO_DIN_1B_REG, (u8)data[i]);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * Configure the hardware FIFO memory boundary.
+ * FIFO occupies addresses [0, fifo_len).
+ */
+static int haptics_configure_fifo_mmap(struct qcom_haptics *h)
+{
+	u32 fifo_len, fifo_units;
+
+	/* Config all memory space for FIFO usage for now */
+	fifo_len = HAP530_MEM_TOTAL_BYTES;
+	fifo_len = ALIGN_DOWN(fifo_len, 64);
+	fifo_units = fifo_len / 64;
+	h->fifo_len = fifo_len;
+
+	return ptn_write(h, HAP_PTN_MMAP_FIFO_REG,
+			 MMAP_FIFO_EXIST_BIT |
+			 FIELD_PREP(MMAP_FIFO_LEN_MASK, fifo_units - 1));
+}
+
+static u32 haptics_gain_scaled_vmax(struct qcom_haptics *h, u32 vmax_mv)
+{
+	u32 v = mult_frac(vmax_mv, h->gain, 0xFFFF);
+
+	return max_t(u32, v, VMAX_STEP_MV);
+}
+
+static void haptics_fifo_irq_enable(struct qcom_haptics *h, bool enable)
+{
+	if (h->irq_enabled == enable)
+		return;
+
+	if (enable)
+		enable_irq(h->fifo_empty_irq);
+	else
+		disable_irq(h->fifo_empty_irq);
+
+	h->irq_enabled = enable;
+}
+
+/*
+ * Must be called with play_lock held.
+ * Clears PLAY_EN and resets any FIFO-specific state.
+ */
+static void haptics_stop_locked(struct qcom_haptics *h)
+{
+	int id = h->cur_effect_id;
+
+	cfg_write(h, HAP_CFG_SPMI_PLAY_REG, 0);
+
+	if (h->effects[id].mode == HAPTICS_FIFO) {
+		ptn_write(h, HAP_PTN_FIFO_EMPTY_CFG_REG, 0);
+		haptics_fifo_irq_enable(h, false);
+		mutex_lock(&h->fifo_lock);
+		h->fifo_data = NULL;
+		mutex_unlock(&h->fifo_lock);
+	}
+}
+
+static int haptics_start_direct_play(struct qcom_haptics *h, int effect_id)
+{
+	struct ff_effect *ffe = &h->input->ff->effects[effect_id];
+	u8 amplitude = (u8)mult_frac((u32)abs(ffe->u.constant.level), 255, 0x7FFF);
+	int ret;
+
+	ret = haptics_clear_faults(h);
+	if (ret)
+		return ret;
+
+	/* Enable auto-resonance for DIRECT_PLAY mode */
+	ret = cfg_update_bits(h, HAP_CFG_AUTORES_CFG_REG,
+			      AUTORES_EN_BIT, AUTORES_EN_BIT);
+	if (ret)
+		return ret;
+
+	ret = haptics_set_vmax(h, haptics_gain_scaled_vmax(h, h->vmax_mv));
+	if (ret)
+		return ret;
+
+	ret = ptn_write(h, HAP_PTN_DIRECT_PLAY_REG, amplitude);
+	if (ret)
+		return ret;
+
+	return cfg_write(h, HAP_CFG_SPMI_PLAY_REG,
+			 PLAY_EN_BIT | FIELD_PREP(PAT_SRC_MASK, PAT_SRC_DIRECT_PLAY));
+}
+
+static int haptics_start_fifo(struct qcom_haptics *h, int effect_id)
+{
+	struct qcom_haptics_effect *eff = &h->effects[effect_id];
+	u32 vmax = eff->vmax_mv ? eff->vmax_mv : h->vmax_mv;
+	u32 init_len;
+	int ret;
+
+	ret = haptics_clear_faults(h);
+	if (ret)
+		return ret;
+
+	/* Disable auto-resonance for FIFO mode */
+	ret = cfg_update_bits(h, HAP_CFG_AUTORES_CFG_REG, AUTORES_EN_BIT, 0);
+	if (ret)
+		return ret;
+
+	ret = haptics_set_vmax(h, haptics_gain_scaled_vmax(h, vmax));
+	if (ret)
+		return ret;
+
+	ret = ptn_update_bits(h, HAP_PTN_FIFO_PLAY_RATE_REG,
+			      FIFO_PLAY_RATE_MASK,
+			      FIELD_PREP(FIFO_PLAY_RATE_MASK, eff->play_rate));
+	if (ret)
+		return ret;
+
+	/* Flush FIFO before loading new data */
+	ret = ptn_write(h, HAP_PTN_MEM_OP_ACCESS_REG, MEM_FLUSH_RELOAD_BIT);
+	if (ret)
+		return ret;
+	ret = ptn_write(h, HAP_PTN_MEM_OP_ACCESS_REG, 0);
+	if (ret)
+		return ret;
+
+	/* Write the initial chunk and initialise streaming state */
+	init_len = min_t(u32, eff->data_len, FIFO_INIT_FILL);
+	ret = haptics_write_fifo_chunk(h, eff->fifo_data, init_len);
+	if (ret)
+		return ret;
+
+	mutex_lock(&h->fifo_lock);
+	h->fifo_data    = eff->fifo_data;
+	h->data_len     = eff->data_len;
+	h->data_written = init_len;
+	h->data_done    = (init_len >= eff->data_len);
+	mutex_unlock(&h->fifo_lock);
+
+	/*
+	 * Set empty threshold.  When threshold > 0 the hardware fires the
+	 * FIFO-empty interrupt when occupancy drops below the threshold,
+	 * allowing the driver to refill.  A threshold of 0 disables the IRQ.
+	 */
+	ret = ptn_write(h, HAP_PTN_FIFO_EMPTY_CFG_REG, h->data_done ? 0 :
+			FIFO_EMPTY_THRESH / FIFO_THRESH_LSB);
+	if (ret)
+		return ret;
+	if (!h->data_done)
+		haptics_fifo_irq_enable(h, true);
+
+	return cfg_write(h, HAP_CFG_SPMI_PLAY_REG,
+			 PLAY_EN_BIT | FIELD_PREP(PAT_SRC_MASK, PAT_SRC_FIFO));
+}
+
+/*
+ * Threaded IRQ handler for the FIFO-empty interrupt.
+ *
+ * While a FIFO play is in progress the hardware fires this interrupt when
+ * the number of samples in the FIFO drops below the programmed threshold.
+ * The handler refills the FIFO from the effect's data buffer.  When all
+ * samples have been written the threshold is set to zero, which suppresses
+ * further interrupts; the hardware drains the remaining samples naturally
+ * and the play work handler stops the engine on the next invocation.
+ */
+static irqreturn_t haptics_fifo_empty_irq(int irq, void *dev_id)
+{
+	struct qcom_haptics *h = dev_id;
+	u32 sts, to_write;
+	int ret;
+
+	ret = regmap_read(h->regmap,
+			  h->cfg_base + HAP_CFG_INT_RT_STS_REG, &sts);
+	if (ret || !(sts & FIFO_EMPTY_BIT))
+		return IRQ_HANDLED;
+
+	mutex_lock(&h->fifo_lock);
+
+	if (!h->fifo_data) {
+		mutex_unlock(&h->fifo_lock);
+		return IRQ_HANDLED;
+	}
+
+	if (h->data_done) {
+		ptn_write(h, HAP_PTN_FIFO_EMPTY_CFG_REG, 0);
+		h->fifo_data = NULL;
+		h->play_request = false;
+		schedule_work(&h->play_work);
+		mutex_unlock(&h->fifo_lock);
+		return IRQ_HANDLED;
+	}
+
+	/* Refill: write the next chunk, conservatively sized to the threshold */
+	to_write = min_t(u32, h->data_len - h->data_written,
+			 h->fifo_len - FIFO_EMPTY_THRESH);
+	haptics_write_fifo_chunk(h, &h->fifo_data[h->data_written], to_write);
+	h->data_written += to_write;
+
+	if (h->data_written >= h->data_len) {
+		/* Last chunk enqueued; disable threshold to stop further IRQs */
+		h->data_done = true;
+		ptn_write(h, HAP_PTN_FIFO_EMPTY_CFG_REG, 0);
+	}
+
+	mutex_unlock(&h->fifo_lock);
+	return IRQ_HANDLED;
+}
+
+static void haptics_play_work(struct work_struct *work)
+{
+	struct qcom_haptics *h = container_of(work, struct qcom_haptics, play_work);
+	int id, ret;
+
+	mutex_lock(&h->play_lock);
+
+	if (!h->play_request) {
+		haptics_stop_locked(h);
+		if (h->pm_ref_held) {
+			pm_runtime_put_autosuspend(h->dev);
+			h->pm_ref_held = false;
+		}
+		goto unlock;
+	}
+
+	if (!h->pm_ref_held) {
+		ret = pm_runtime_resume_and_get(h->dev);
+		if (ret < 0) {
+			dev_err(h->dev, "failed to resume device: %d\n", ret);
+			goto unlock;
+		}
+		h->pm_ref_held = true;
+	}
+
+	id = h->cur_effect_id;
+	switch (h->effects[id].mode) {
+	case HAPTICS_DIRECT_PLAY:
+		ret = haptics_start_direct_play(h, id);
+		break;
+	case HAPTICS_FIFO:
+		ret = haptics_start_fifo(h, id);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	if (ret) {
+		dev_err(h->dev, "failed to start effect %d: %d\n", id, ret);
+		if (h->pm_ref_held) {
+			pm_runtime_put_autosuspend(h->dev);
+			h->pm_ref_held = false;
+		}
+	}
+
+unlock:
+	mutex_unlock(&h->play_lock);
+}
+
+static int haptics_upload_effect(struct input_dev *dev,
+				 struct ff_effect *effect,
+				 struct ff_effect *old)
+{
+	struct qcom_haptics *h = input_get_drvdata(dev);
+	struct qcom_haptics_effect *priv;
+	int id = effect->id;
+	u32 data_len;
+	s16 *buf;
+	s8 *fifo;
+
+	if (id < 0 || id >= HAPTICS_MAX_EFFECTS)
+		return -EINVAL;
+
+	priv = &h->effects[id];
+
+	switch (effect->type) {
+	case FF_CONSTANT:
+		kfree(priv->fifo_data);
+		priv->fifo_data = NULL;
+		priv->data_len  = 0;
+		priv->mode = HAPTICS_DIRECT_PLAY;
+		return 0;
+
+	case FF_PERIODIC:
+		if (effect->u.periodic.waveform != FF_CUSTOM)
+			return -EINVAL;
+		/*
+		 * Minimum 3 elements: play-rate code + vmax + at least one sample.
+		 * No upper bound: the FIFO is refilled continuously from the IRQ
+		 * handler, so any length of PCM data is supported.
+		 */
+		if (effect->u.periodic.custom_len < 3)
+			return -EINVAL;
+
+		buf = memdup_array_user(effect->u.periodic.custom_data,
+					effect->u.periodic.custom_len,
+					sizeof(s16));
+		if (IS_ERR(buf))
+			return PTR_ERR(buf);
+
+		if (buf[CUSTOM_DATA_RATE_IDX] > PLAY_RATE_MAX) {
+			kfree(buf);
+			return -EINVAL;
+		}
+
+		data_len = effect->u.periodic.custom_len - CUSTOM_DATA_SAMPLE_START;
+		fifo = kmalloc(data_len, GFP_KERNEL);
+		if (!fifo) {
+			kfree(buf);
+			return -ENOMEM;
+		}
+
+		/* Pack: one s8 sample per s16 slot (lower byte) */
+		for (int i = 0; i < data_len; i++)
+			fifo[i] = (s8)buf[CUSTOM_DATA_SAMPLE_START + i];
+
+		kfree(priv->fifo_data);
+		priv->fifo_data = fifo;
+		priv->data_len  = data_len;
+		priv->play_rate = (u8)buf[CUSTOM_DATA_RATE_IDX];
+		priv->vmax_mv   = (u32)clamp_val(buf[CUSTOM_DATA_VMAX_IDX], 0, VMAX_MV_MAX);
+		priv->mode = HAPTICS_FIFO;
+
+		kfree(buf);
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int haptics_playback(struct input_dev *dev, int effect_id, int val)
+{
+	struct qcom_haptics *h = input_get_drvdata(dev);
+
+	h->cur_effect_id = effect_id;
+	h->play_request  = (val > 0);
+	schedule_work(&h->play_work);
+
+	return 0;
+}
+
+static int haptics_erase(struct input_dev *dev, int effect_id)
+{
+	struct qcom_haptics *h = input_get_drvdata(dev);
+	struct qcom_haptics_effect *priv = &h->effects[effect_id];
+
+	kfree(priv->fifo_data);
+	priv->fifo_data = NULL;
+	priv->data_len  = 0;
+
+	return 0;
+}
+
+static void haptics_set_gain(struct input_dev *dev, u16 gain)
+{
+	struct qcom_haptics *h = input_get_drvdata(dev);
+
+	h->gain = gain;
+}
+
+static int qcom_haptics_probe(struct platform_device *pdev)
+{
+	struct qcom_haptics *h;
+	struct input_dev *input;
+	struct ff_device *ff;
+	u32 regs[2], vmax_uv;
+	int ret, irq;
+
+	h = devm_kzalloc(&pdev->dev, sizeof(*h), GFP_KERNEL);
+	if (!h)
+		return -ENOMEM;
+
+	h->dev = &pdev->dev;
+
+	h->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!h->regmap)
+		return dev_err_probe(h->dev, -ENODEV, "no regmap from parent\n");
+
+	ret = device_property_read_u32_array(h->dev, "reg", regs, ARRAY_SIZE(regs));
+	if (ret)
+		return dev_err_probe(h->dev, ret, "failed to read 'reg' property\n");
+
+	h->cfg_base = regs[0];
+	h->ptn_base = regs[1];
+
+	ret = device_property_read_u32(h->dev, "qcom,lra-period-us", &h->t_lra_us);
+	if (ret)
+		return dev_err_probe(h->dev, ret, "missing qcom,lra-period-us\n");
+
+	ret = device_property_read_u32(h->dev, "qcom,vmax-microvolt", &vmax_uv);
+	if (ret)
+		return dev_err_probe(h->dev, ret, "missing qcom,vmax-microvolt\n");
+
+	h->vmax_mv = clamp(vmax_uv / 1000, (u32)VMAX_STEP_MV, (u32)VMAX_MV_MAX);
+
+	ret = haptics_config_lra_period(h);
+	if (ret)
+		return ret;
+
+	ret = haptics_configure_autores(h);
+	if (ret)
+		return ret;
+
+	ret = haptics_set_vmax(h, h->vmax_mv);
+	if (ret)
+		return ret;
+
+	ret = haptics_configure_fifo_mmap(h);
+	if (ret)
+		return ret;
+
+	mutex_init(&h->play_lock);
+	mutex_init(&h->fifo_lock);
+	INIT_WORK(&h->play_work, haptics_play_work);
+	h->gain = 0xFFFF;
+
+	irq = platform_get_irq_byname(pdev, "fifo-empty");
+	if (irq < 0)
+		return dev_err_probe(h->dev, irq, "failed to get fifo-empty IRQ\n");
+
+	ret = devm_request_threaded_irq(h->dev, irq, NULL,
+					haptics_fifo_empty_irq,
+					IRQF_ONESHOT,
+					"qcom-haptics-fifo-empty", h);
+	if (ret)
+		return dev_err_probe(h->dev, ret, "failed to request fifo-empty IRQ\n");
+
+	h->fifo_empty_irq = irq;
+	disable_irq_nosync(irq);
+
+	input = devm_input_allocate_device(h->dev);
+	if (!input)
+		return -ENOMEM;
+
+	input->name = "qcom-spmi-haptics";
+	input_set_drvdata(input, h);
+	h->input = input;
+
+	input_set_capability(input, EV_FF, FF_CONSTANT);
+	input_set_capability(input, EV_FF, FF_PERIODIC);
+	input_set_capability(input, EV_FF, FF_CUSTOM);
+	input_set_capability(input, EV_FF, FF_GAIN);
+
+	ret = input_ff_create(input, HAPTICS_MAX_EFFECTS);
+	if (ret)
+		return ret;
+
+	ff = input->ff;
+	ff->upload   = haptics_upload_effect;
+	ff->playback = haptics_playback;
+	ff->erase    = haptics_erase;
+	ff->set_gain = haptics_set_gain;
+
+	pm_runtime_get_noresume(h->dev);
+	pm_runtime_use_autosuspend(h->dev);
+	pm_runtime_set_autosuspend_delay(h->dev, HAPTICS_AUTOSUSPEND_MS);
+	devm_pm_runtime_set_active_enabled(h->dev);
+	pm_runtime_put_autosuspend(h->dev);
+
+	ret = input_register_device(input);
+	if (ret) {
+		input_ff_destroy(input);
+		return dev_err_probe(h->dev, ret, "failed to register input device\n");
+	}
+
+	platform_set_drvdata(pdev, h);
+
+	return 0;
+}
+
+static void qcom_haptics_remove(struct platform_device *pdev)
+{
+	struct qcom_haptics *h = platform_get_drvdata(pdev);
+	int i;
+
+	pm_runtime_disable(&pdev->dev);
+	pm_runtime_set_suspended(&pdev->dev);
+
+	mutex_lock(&h->play_lock);
+	haptics_stop_locked(h);
+	mutex_unlock(&h->play_lock);
+
+	haptics_enable_module(h, false);
+	cancel_work_sync(&h->play_work);
+	for (i = 0; i < HAPTICS_MAX_EFFECTS; i++) {
+		kfree(h->effects[i].fifo_data);
+		h->effects[i].fifo_data = NULL;
+	}
+
+	input_unregister_device(h->input);
+}
+
+static int qcom_haptics_runtime_suspend(struct device *dev)
+{
+	struct qcom_haptics *h = dev_get_drvdata(dev);
+
+	return haptics_enable_module(h, false);
+}
+
+static int qcom_haptics_runtime_resume(struct device *dev)
+{
+	struct qcom_haptics *h = dev_get_drvdata(dev);
+
+	return haptics_enable_module(h, true);
+}
+
+static int qcom_haptics_suspend(struct device *dev)
+{
+	struct qcom_haptics *h = dev_get_drvdata(dev);
+
+	mutex_lock(&h->play_lock);
+	haptics_stop_locked(h);
+	if (h->pm_ref_held) {
+		pm_runtime_put_noidle(dev);
+		h->pm_ref_held = false;
+	}
+	mutex_unlock(&h->play_lock);
+
+	cancel_work_sync(&h->play_work);
+	return pm_runtime_force_suspend(dev);
+}
+
+static int qcom_haptics_resume(struct device *dev)
+{
+	return pm_runtime_force_resume(dev);
+}
+
+static const struct dev_pm_ops qcom_haptics_pm_ops = {
+	SYSTEM_SLEEP_PM_OPS(qcom_haptics_suspend, qcom_haptics_resume)
+	RUNTIME_PM_OPS(qcom_haptics_runtime_suspend, qcom_haptics_runtime_resume,
+		       NULL)
+};
+
+static const struct of_device_id qcom_haptics_of_match[] = {
+	{ .compatible = "qcom,spmi-haptics" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, qcom_haptics_of_match);
+
+static struct platform_driver qcom_haptics_driver = {
+	.probe  = qcom_haptics_probe,
+	.remove = qcom_haptics_remove,
+	.driver = {
+		.name		= "qcom-spmi-haptics",
+		.of_match_table	= qcom_haptics_of_match,
+		.pm		= pm_ptr(&qcom_haptics_pm_ops),
+	},
+};
+module_platform_driver(qcom_haptics_driver);
+
+MODULE_DESCRIPTION("Qualcomm SPMI PMIC Haptics driver");
+MODULE_LICENSE("GPL");

-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH v6 1/2] dt-bindings: bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
From: Sunyun Yang @ 2026-06-25  2:01 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
	dmitry.baryshkov, maarten.lankhorst, rfoss, mripard,
	Laurent.pinchart, tzimmermann, jonas, jernej.skrabec, devicetree,
	dri-devel, linux-kernel, xmzhu, xmzhu, rlyu, xbpeng
In-Reply-To: <CAFQXuNajWT31q1MccwTDa074_7=6tfaz-FOmP-tx_q83R60QfQ@mail.gmail.com>

Sunyun Yang <syyang@lontium.com> 于2026年6月25日周四 08:52写道:
>
> Krzysztof Kozlowski <krzk@kernel.org> 于2026年6月24日周三 22:05写道:
> >
> > On 11/05/2026 05:28, Sunyun Yang wrote:
> > > <syyang@lontium.com> 于2026年5月8日周五 22:25写道:
> > >>
> > >> From: Sunyun Yang <syyang@lontium.com>
> > >>
> > >> LT9611C(EX/UXD) is an I2C-controlled chip that Receiver signal/dual port
> > >> mipi dsi and output hdmi, differences in hardware features:
> > >> - LT9611C: supports 1-port mipi dsi to hdmi 1.4
> > >> - LT9611EX: supports 2-port mipi dsi to hdmi 1.4
> > >> - LT9611UXD: supports 2-port mipi dsi to hdmi 1.4/2.0
> > >>
> > >> Signed-off-by: Sunyun Yang <syyang@lontium.com>
> > >> ---
> > >>  .../bindings/display/bridge/lontium,lt9611.yaml           | 8 ++++++--
> > >>  1 file changed, 6 insertions(+), 2 deletions(-)
> > >>
> > >> diff --git a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml
> > >> index 429a06057ae8..e0821a63d9d7 100644
> > >> --- a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml
> > >> +++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml
> > >> @@ -4,19 +4,23 @@
> > >>  $id: http://devicetree.org/schemas/display/bridge/lontium,lt9611.yaml#
> > >>  $schema: http://devicetree.org/meta-schemas/core.yaml#
> > >>
> > >> -title: Lontium LT9611(UXC) 2 Port MIPI to HDMI Bridge
> > >> +title: Lontium LT9611(UXC/C/EX/UXD) 2 Port MIPI DSI to HDMI Bridge
> > >>
> > >>  maintainers:
> > >>    - Vinod Koul <vkoul@kernel.org>
> > >>
> > >>  description: |
> > >> -  The LT9611 and LT9611UXC are bridge devices which convert DSI to HDMI
> > >> +  The LT9611、LT9611UXC、LT9611C、LT9611EX and LT9611UXD
> > >> +  are bridge devices which convert DSI to HDMI
> > >>
> > >>  properties:
> > >>    compatible:
> > >>      enum:
> > >>        - lontium,lt9611
> > >> +      - lontium,lt9611c
> > >> +      - lontium,lt9611ex
> > >>        - lontium,lt9611uxc
> > >> +      - lontium,lt9611uxd
> > >>
> > >>    reg:
> > >>      maxItems: 1
> > >> --
> > >
> > > Gentle ping.
> > > Thanks.
> >
> > Except mess with threading, your patchset does not build, when applied
> > on next-20260618.
> >
> > What is the base of this?
> >
>
> Thanks for testing my patchset on next-20260618.
>
> The base of this series is v7.0.
>
Krzysztof,   Is Sashiko AI review required before merging a patch now?

> > Best regards,
> > Krzysztof

^ permalink raw reply

* RE: [PATCH v6 9/9] arm64: dts: freescale: imx95: Add video codec node
From: Nas Chung @ 2026-06-25  2:11 UTC (permalink / raw)
  To: Francesco Dolcini
  Cc: mchehab@kernel.org, hverkuil@xs4all.nl, robh@kernel.org,
	krzk+dt@kernel.org, conor+dt@kernel.org, shawnguo@kernel.org,
	s.hauer@pengutronix.de, linux-media@vger.kernel.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-imx@nxp.com, linux-arm-kernel@lists.infradead.org,
	jackson.lee, lafley.kim, marek.vasut@mailbox.org
In-Reply-To: <20260624115050.GA38214@francesco-nb>

Hi, Francesco.

>-----Original Message-----
>From: Francesco Dolcini <francesco@dolcini.it>
>Sent: Wednesday, June 24, 2026 8:51 PM
>To: Nas Chung <nas.chung@chipsnmedia.com>
>Cc: mchehab@kernel.org; hverkuil@xs4all.nl; robh@kernel.org;
>krzk+dt@kernel.org; conor+dt@kernel.org; shawnguo@kernel.org;
>s.hauer@pengutronix.de; linux-media@vger.kernel.org;
>devicetree@vger.kernel.org; linux-kernel@vger.kernel.org; linux-imx@nxp.com;
>linux-arm-kernel@lists.infradead.org; jackson.lee
><jackson.lee@chipsnmedia.com>; lafley.kim <lafley.kim@chipsnmedia.com>;
>marek.vasut@mailbox.org
>Subject: Re: [PATCH v6 9/9] arm64: dts: freescale: imx95: Add video codec
>node
>
>On Wed, Jun 24, 2026 at 04:20:43PM +0900, Nas Chung wrote:
>> Add the Chips and Media wave633 video codec node on IMX95 SoCs.
>>
>> Signed-off-by: Nas Chung <nas.chung@chipsnmedia.com>
>> ---
>>  .../boot/dts/freescale/imx95-19x19-evk.dts    | 11 ++++++
>>  arch/arm64/boot/dts/freescale/imx95.dtsi      | 36 +++++++++++++++++++
>>  2 files changed, 47 insertions(+)
>>
>> diff --git a/arch/arm64/boot/dts/freescale/imx95-19x19-evk.dts
>b/arch/arm64/boot/dts/freescale/imx95-19x19-evk.dts
>> index 041fd838fabb..7edd1c69966a 100644
>> --- a/arch/arm64/boot/dts/freescale/imx95-19x19-evk.dts
>> +++ b/arch/arm64/boot/dts/freescale/imx95-19x19-evk.dts
>> @@ -76,6 +76,11 @@ linux_cma: linux,cma {
>>  			linux,cma-default;
>>  			reusable;
>>  		};
>> +
>> +		vpu_boot: memory@a0000000 {
>> +			reg = <0 0xa0000000 0 0x100000>;
>> +			no-map;
>> +		};
>>  	};
>>
>>  	flexcan1_phy: can-phy0 {
>> @@ -1142,3 +1147,9 @@ &tpm6 {
>>  	pinctrl-0 = <&pinctrl_tpm6>;
>>  	status = "okay";
>>  };
>> +
>> +&vpu {
>> +	memory-region = <&vpu_boot>;
>> +	sram = <&sram1>;
>
>Can the `sram` node moved to imx95.dtsi or not?

Thanks for your feedback.

Agreed, I'll move the sram property to imx95.dtsi in v7.

Thanks.
Nas.

>
>Francesco

^ permalink raw reply

* Re: [PATCH v2 3/3] input: misc: Add Qualcomm SPMI PMIC haptics driver
From: sashiko-bot @ 2026-06-25  2:13 UTC (permalink / raw)
  To: Fenglin Wu; +Cc: linux-input, dmitry.torokhov, conor+dt, devicetree, robh
In-Reply-To: <20260624-qcom-spmi-haptics-v2-3-b9118e60f3e3@oss.qualcomm.com>

Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [High] Concurrent effect uploads or erasures free `fifo_data` while it is actively used by the playback worker or IRQ handler, leading to a Use-After-Free.
- [High] System suspend races with the asynchronous playback worker, leaving the hardware active during sleep and leaking a PM runtime reference.
- [High] Incorrect teardown ordering allows userspace to trigger Use-After-Free of driver structures after removal.
- [High] The driver fails to terminate FIFO playback when finished, permanently leaking a PM runtime reference.
- [Medium] Unbounded user-controlled kmalloc size allows memory exhaustion.
- [Medium] Missing signedness check on the custom play rate allows negative values to bypass boundary checks.
- [Medium] Lockless, non-atomic accesses to `play_request` and `cur_effect_id` cause new play requests to be lost.
- [Medium] The runtime PM usage counter is leaked during device removal if playback is active.
--

commit 06679c85ddc3489b6ff4ed3aec7d938287e570cd
Author: Fenglin Wu <fenglin.wu@oss.qualcomm.com>

input: misc: Add Qualcomm SPMI PMIC haptics driver

Add an initial driver for the Qualcomm PMIH0108 PMIC haptics module,
named as HAP530_HV. This module supports several play modes, including
DIRECT_PLAY, FIFO, PAT_MEM, and SWR, each with distinct data sourcing
and hardware data handling logic. Currently, the driver provides support
for two play modes using the input force-feedback framework: FF_CONSTANT
effect for DIRECT_PLAY mode and FF_PERIODIC effect with FF_CUSTOM
waveform for FIFO mode.

> diff --git a/drivers/input/misc/qcom-spmi-haptics.c b/drivers/input/misc/qcom-spmi-haptics.c
> new file mode 100644
> index 0000000000000..4b27638df9605
> --- /dev/null
> +++ b/drivers/input/misc/qcom-spmi-haptics.c

[ ... ]

> +static irqreturn_t haptics_fifo_empty_irq(int irq, void *dev_id)
> +{
> +	struct qcom_haptics *h = dev_id;
> +	u32 sts, to_write;
> +	int ret;
> +
> +	ret = regmap_read(h->regmap,
> +			  h->cfg_base + HAP_CFG_INT_RT_STS_REG, &sts);
> +	if (ret || !(sts & FIFO_EMPTY_BIT))
> +		return IRQ_HANDLED;
> +
> +	mutex_lock(&h->fifo_lock);
> +
> +	if (!h->fifo_data) {
> +		mutex_unlock(&h->fifo_lock);
> +		return IRQ_HANDLED;
> +	}
> +
> +	if (h->data_done) {
> +		ptn_write(h, HAP_PTN_FIFO_EMPTY_CFG_REG, 0);
> +		h->fifo_data = NULL;
> +		h->play_request = false;
> +		schedule_work(&h->play_work);
> +		mutex_unlock(&h->fifo_lock);
> +		return IRQ_HANDLED;
> +	}
> +
> +	/* Refill: write the next chunk, conservatively sized to the threshold */
> +	to_write = min_t(u32, h->data_len - h->data_written,
> +			 h->fifo_len - FIFO_EMPTY_THRESH);
> +	haptics_write_fifo_chunk(h, &h->fifo_data[h->data_written], to_write);

[Severity: High]
Does this code dereference a freed pointer?

A userspace process can start a FIFO effect, then concurrently call 
EVIOCSFF (to re-upload) or EVIOCRMFF (to erase) the effect.

The input core calls haptics_erase() or haptics_upload_effect(), which 
immediately execute kfree(priv->fifo_data).

Since the IRQ handler or worker continues to use h->fifo_data under 
h->fifo_lock, it appears it will dereference the freed memory.

> +	h->data_written += to_write;
> +
> +	if (h->data_written >= h->data_len) {
> +		/* Last chunk enqueued; disable threshold to stop further IRQs */
> +		h->data_done = true;
> +		ptn_write(h, HAP_PTN_FIFO_EMPTY_CFG_REG, 0);

[Severity: High]
Does this fail to terminate FIFO playback when finished?

When the final chunk is written, the code sets FIFO_EMPTY_CFG_REG to 0,
disabling the empty interrupt.

Because the interrupt is disabled, the handler will never fire again to
process the h->data_done block at the start of the function. This leaves
the hardware active indefinitely and leaks the PM runtime usage counter.

> +	}
> +
> +	mutex_unlock(&h->fifo_lock);
> +	return IRQ_HANDLED;
> +}

[ ... ]

> +static int haptics_upload_effect(struct input_dev *dev,
> +				 struct ff_effect *effect,
> +				 struct ff_effect *old)
> +{
> +	struct qcom_haptics *h = input_get_drvdata(dev);
> +	struct qcom_haptics_effect *priv;
> +	int id = effect->id;
> +	u32 data_len;
> +	s16 *buf;
> +	s8 *fifo;
> +
> +	if (id < 0 || id >= HAPTICS_MAX_EFFECTS)
> +		return -EINVAL;
> +
> +	priv = &h->effects[id];
> +
> +	switch (effect->type) {
> +	case FF_CONSTANT:
> +		kfree(priv->fifo_data);
> +		priv->fifo_data = NULL;
> +		priv->data_len  = 0;
> +		priv->mode = HAPTICS_DIRECT_PLAY;
> +		return 0;
> +
> +	case FF_PERIODIC:
> +		if (effect->u.periodic.waveform != FF_CUSTOM)
> +			return -EINVAL;
> +		/*
> +		 * Minimum 3 elements: play-rate code + vmax + at least one sample.
> +		 * No upper bound: the FIFO is refilled continuously from the IRQ
> +		 * handler, so any length of PCM data is supported.
> +		 */
> +		if (effect->u.periodic.custom_len < 3)
> +			return -EINVAL;
> +
> +		buf = memdup_array_user(effect->u.periodic.custom_data,
> +					effect->u.periodic.custom_len,
> +					sizeof(s16));

[Severity: Medium]
Is there a missing upper bound check on custom_len?

A local user with write access to the input device can provide a very
large custom_len, and this directly allocates kernel memory without an
upper bound, which could lead to memory exhaustion.

> +		if (IS_ERR(buf))
> +			return PTR_ERR(buf);
> +
> +		if (buf[CUSTOM_DATA_RATE_IDX] > PLAY_RATE_MAX) {

[Severity: Medium]
Can negative values bypass this boundary check?

buf is an array of signed 16-bit integers. A user passing a negative value 
would bypass this check, get cast to u8 later, and be written to the
hardware registers as an out-of-bounds unsigned value.

> +			kfree(buf);
> +			return -EINVAL;
> +		}

[ ... ]

> +static int haptics_playback(struct input_dev *dev, int effect_id, int val)
> +{
> +	struct qcom_haptics *h = input_get_drvdata(dev);
> +
> +	h->cur_effect_id = effect_id;
> +	h->play_request  = (val > 0);

[Severity: Medium]
Are play requests occasionally lost due to lockless accesses?

If the IRQ handler overwrites h->play_request to false immediately after a
new play event from userspace sets it to true here, the new request seems
to be lost. The worker will see play_request == false and stop the
hardware instead of starting the new effect.

> +	schedule_work(&h->play_work);
> +
> +	return 0;
> +}

[ ... ]

> +static void qcom_haptics_remove(struct platform_device *pdev)
> +{
> +	struct qcom_haptics *h = platform_get_drvdata(pdev);
> +	int i;
> +
> +	pm_runtime_disable(&pdev->dev);
> +	pm_runtime_set_suspended(&pdev->dev);
> +
> +	mutex_lock(&h->play_lock);
> +	haptics_stop_locked(h);
> +	mutex_unlock(&h->play_lock);

[Severity: Medium]
Does this leak the runtime PM usage counter?

If remove is invoked while a haptics effect is actively playing, 
h->pm_ref_held is true. This stops the hardware but does not call
pm_runtime_put_noidle(dev) to release the reference, leaving the PM
state unbalanced.

> +
> +	haptics_enable_module(h, false);
> +	cancel_work_sync(&h->play_work);
> +	for (i = 0; i < HAPTICS_MAX_EFFECTS; i++) {
> +		kfree(h->effects[i].fifo_data);
> +		h->effects[i].fifo_data = NULL;
> +	}
> +
> +	input_unregister_device(h->input);

[Severity: High]
Is it safe to tear down resources before unregistering the input device?

Because the input device is still registered and active, an unprivileged
user can concurrently issue an ioctl (like EVIOCSFF or EVIOCRMFF) on the
open event node. This could access the freed effect structures or schedule 
h->play_work after they have been destroyed.

> +}

[ ... ]

> +static int qcom_haptics_suspend(struct device *dev)
> +{
> +	struct qcom_haptics *h = dev_get_drvdata(dev);
> +
> +	mutex_lock(&h->play_lock);
> +	haptics_stop_locked(h);
> +	if (h->pm_ref_held) {
> +		pm_runtime_put_noidle(dev);
> +		h->pm_ref_held = false;
> +	}
> +	mutex_unlock(&h->play_lock);

[Severity: High]
Does this system suspend race with the asynchronous playback worker?

If userspace requests playback right before system suspend, h->play_request 
is set to true and play_work is scheduled.

This suspend function stops the hardware but forgets to clear
h->play_request.

When this drops the lock, play_work can execute, see play_request is still 
true, and re-enable the hardware just before the system suspends.

> +
> +	cancel_work_sync(&h->play_work);
> +	return pm_runtime_force_suspend(dev);
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260624-qcom-spmi-haptics-v2-0-b9118e60f3e3@oss.qualcomm.com?part=3

^ permalink raw reply

* [PATCH 2/2] regualtor: rtq2208: Initiate the default MTP_SEL state by hardware register
From: cy_huang @ 2026-06-25  2:18 UTC (permalink / raw)
  To: Mark Brown, Krzysztof Kozlowski
  Cc: Rob Herring, Conor Dooley, Liam Girdwood, ChiYuan Huang,
	Yoon Dong Min, edward_kim, devicetree, linux-kernel
In-Reply-To: <cover.1782353659.git.cy_huang@richtek.com>

From: ChiYuan Huang <cy_huang@richtek.com>

Read the initial MTP_SEL state by hardware register to prevent the wrong
specified property value from the conflict of hardware pin assignment.

Signed-off-by: ChiYuan Huang <cy_huang@richtek.com>
---
 drivers/regulator/rtq2208-regulator.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/regulator/rtq2208-regulator.c b/drivers/regulator/rtq2208-regulator.c
index f669a562f036..7fe082def494 100644
--- a/drivers/regulator/rtq2208-regulator.c
+++ b/drivers/regulator/rtq2208-regulator.c
@@ -12,6 +12,7 @@
 #include <linux/mod_devicetable.h>
 
 /* Register */
+#define RTQ2208_REG_FSOUTB_CNTL			0x11
 #define RTQ2208_REG_GLOBAL_INT1			0x12
 #define RTQ2208_REG_FLT_RECORDBUCK_CB		0x18
 #define RTQ2208_REG_GLOBAL_INT1_MASK		0x1D
@@ -34,6 +35,7 @@
 #define RTQ2208_REG_HIDDEN1			0xFF
 
 /* Mask */
+#define RTQ2208_MTP_SEL_RO_MASK			BIT(7)
 #define RTQ2208_BUCK_NR_MTP_SEL_MASK		GENMASK(7, 0)
 #define RTQ2208_BUCK_EN_NR_MTP_SEL0_MASK	BIT(0)
 #define RTQ2208_BUCK_EN_NR_MTP_SEL1_MASK	BIT(1)
@@ -465,10 +467,13 @@ static int rtq2208_parse_regulator_dt_data(int n_regulator, const unsigned int *
 		struct rtq2208_regulator_desc *rdesc[RTQ2208_LDO_MAX], struct device *dev,
 		unsigned int ldo1_fixed, unsigned int ldo2_fixed)
 {
+	struct regmap *regmap = dev_get_regmap(dev, NULL);
 	int mtp_sel, i, idx;
 
 	/* get mtp_sel0 or mtp_sel1 */
-	mtp_sel = device_property_read_bool(dev, "richtek,mtp-sel-high");
+	mtp_sel = regmap_test_bits(regmap, RTQ2208_REG_FSOUTB_CNTL, RTQ2208_MTP_SEL_RO_MASK);
+	if (mtp_sel < 0)
+		return dev_err_probe(dev, mtp_sel, "Failed to init mtp_sel state\n");
 
 	for (i = 0; i < n_regulator; i++) {
 		idx = regulator_idx_table[i];
-- 
2.43.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox