Devicetree
 help / color / mirror / Atom feed
* RE: [PATCH v2 2/3] remoteproc: imx_rproc: Pass bootaddr to SM CPU/LMM reset vector
From: Peng Fan @ 2026-04-11  3:00 UTC (permalink / raw)
  To: Mathieu Poirier, Peng Fan (OSS)
  Cc: Bjorn Andersson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Frank Li, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	Daniel Baluta, linux-remoteproc@vger.kernel.org,
	devicetree@vger.kernel.org, imx@lists.linux.dev,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org
In-Reply-To: <adkcugNgyrkHtUML@p14s>

> Subject: Re: [PATCH v2 2/3] remoteproc: imx_rproc: Pass bootaddr to
> SM CPU/LMM reset vector
> 
> On Thu, Apr 09, 2026 at 08:30:54AM +0800, Peng Fan wrote:
> > On Wed, Apr 08, 2026 at 09:46:32AM -0600, Mathieu Poirier wrote:
> > >On Wed, Apr 08, 2026 at 01:30:16AM +0000, Peng Fan wrote:
> > >> > Subject: Re: [PATCH v2 2/3] remoteproc: imx_rproc: Pass
> bootaddr
> > >> > to SM CPU/LMM reset vector
> > >> >
> > >> [...]
> > >> >
> > >> > >
> > >> > > Aligning the ELF entry point with the hardware reset base on
> > >> > Cortex‑M
> > >> > > systems is possible, but it comes with several risks.
> > >> >
> > >> > I'm not asking to align the ELF entry point with the hardware
> reset base.
> > >> > All I want is to have the correct start address embedded in the
> > >> > ELF file to avoid having to use a mask.
> > >>
> > >> I see, per my understanding:
> > >> FreeRTOS typically exposes __isr_vector, which corresponds to the
> > >> hardware reset / vector table base.
> > >> Zephyr (Cortex‑M) exposes _vector_table, which serves the same
> purpose.
> > >> I am not certain about other RTOSes, but the pattern seems
> consistent:
> > >> the vector table base is already available as a named ELF symbol.
> > >>
> > >> Given that, if the preferred approach is to parse the ELF and
> > >> explicitly retrieve the hardware reset base, I can update the
> implementation accordingly.
> > >> If you prefer to parse the elf file to get the hardware reset base,
> > >> I could update to use them.
> > >>
> > >> Options1: Something as below:
> > >> 1. Include rproc_elf_find_symbol in remoteproc_elf_loader.c 2.
> Use
> > >> below in imx_rproc.c ret = rproc_elf_find_symbol(rproc, fw,
> > >> "__isr_vector", &vector_base); if (ret)
> > >>     ret = rproc_elf_find_symbol(rproc, fw, "__vector_table",
> > >> &vector_base);
> > >>
> > >> if (!ret)
> > >>     rproc->bootaddr = vector_base
> > >> else
> > >>    dev_info(dev, "no __isr_vector or __vector_table\n")
> > >
> > >No
> >
> > If your concern is about rproc->bootaddr, I could introduce
> > imx_rproc->vector_base for i.MX.  Please help detail a bit.
> >
> > >
> > >>
> > >> This makes the hardware reset base explicit, avoids masking
> e_entry.
> > >>
> > >> Option 2: User‑provided reset symbol via sysfs As an alternative,
> > >> we could expose a sysfs attribute, e.g. reset_symbol, allowing
> > >> users to specify the symbol name to be used as the reset base:
> > >>
> > >> echo __isr_vector >
> /sys/class/remoteproc/remoteprocX/reset_symbol
> > >>
> > >
> > >Definitely not.
> > >
> > >The definition of e_entry in the specification is clear, i.e "the
> > >address of the entry point from where the process starts executing".
> > >If masking is required because the tool that puts the image together
> > >gets the wrong address, then it should be fixed.
> >
> > The hardware reset base is the address from which the hardware
> fetches
> > the initial stack pointer and program counter values and loads them
> > into the SP and PC registers.  In contrast, bootaddr (i.e. e_entry)
> > represents the address at which the CPU starts executing code (the
> PC
> > value after reset). As you pointed out earlier, this distinction is clear.
> >
> > In our case, we need to obtain the hardware reset base and pass that
> > value to the system firmware. However, e_entry should not be set to
> > the hardware reset base. Doing so would introduce the issues I
> > described in [1]. This means we should not modify the Zephyr or
> > FreeRTOS build outputs to make e_entry equal to the hardware reset
> base.
> 
> 
> As I said earlier, I am _not_ suggesting to make e_entry equal to the
> hardware reset base.

Let me try to restate my understanding more precisely and please
correct me if I am still missing the point.

From your comment:
"
If masking is required because the tool that puts the image together gets the
wrong address, then it should be fixed.
"

I understand this as saying that masking e_entry is not acceptable, because
e_entry already has a clear and correct meaning: it is the execution entry
address, and the kernel should not reinterpret or “fix up” that value.
At the same time, we still need to provide the hardware reset vector base
to the system firmware, and that value is distinct from e_entry.

On i.MX94/5 platforms the reset base is software‑programmable, but that
information is not represented by e_entry, nor is there currently a
separate place in the remoteproc framework to convey a reset‑vector
base independent of the execution entry point.

Given these constraints, I see limited options on the kernel side. 

One conservative approach would be to rely on a fixed, platform‑defined
reset base for the affected SoCs. And update RTOS linking script to put
the vector to the location of fixed hardware reset base.

Thanks,
Peng

> 
> We are going in circles here.
> 
> >
> > Given these constraints, the feasible solutions I can see are either:
> > - option 1 (explicitly retrieving the hardware reset base), or
> > - continuing to use masking.
> >
> > Please suggest.
> >
> > [1]
> >
> https://eur01.safelinks.protection.outlook.com/?url=https%3A%2F%2F
> lore
> > .kernel.org%2Fall%2Facs2PAZq2k3zjmDW%40shlinux89%2F&data=0
> 5%7C02%7Cpen
> >
> g.fan%40nxp.com%7C8a5ce35d492b4adb2d3b08de97192cbb%7C686
> ea1d3bc2b4c6fa
> >
> 92cd99c5c301635%7C0%7C0%7C639114331565834960%7CUnknow
> n%7CTWFpbGZsb3d8e
> >
> yJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsI
> kFOIjoiTWF
> >
> pbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=Pnkirz3BMEuLsJU9
> MHQNon84HIyMX
> > 08x1wCK04dS7VU%3D&reserved=0
> >
> > Thanks,
> > Peng
> >
> > >
> > >> The remoteproc core would then resolve that symbol from the ELF
> and
> > >> set rproc->bootaddr accordingly.
> > >> This provides maximum flexibility but does introduce a new
> > >> user‑visible ABI, so I see it more as an opt‑in or fallback
> mechanism.
> > >>
> > >> Please let me know which approach you prefer, and I will update
> > >> this series accordingly in v3..
> > >>
> > >> Thanks,
> > >> Peng.
> > >>
> > >>
> > >> >
> > >> > > 1, Semantic mismatch (ELF vs. hardware behavior) 2,
> Debuggers
> > >> > > may attempt to set breakpoints or start execution at the entry
> > >> > > symbol
> > >> > >

^ permalink raw reply

* Re: [PATCH v5 4/5] remoteproc: qcom: pas: Add late attach support for subsystems
From: Bjorn Andersson @ 2026-04-11  2:59 UTC (permalink / raw)
  To: Jingyi Wang
  Cc: Mathieu Poirier, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Manivannan Sadhasivam, Luca Weiss, Bartosz Golaszewski,
	Konrad Dybcio, aiqun.yu, tingwei.zhang, trilok.soni, yijie.yang,
	linux-arm-msm, linux-remoteproc, devicetree, linux-kernel,
	Gokul Krishna Krishnakumar
In-Reply-To: <20260409-knp-soccp-v5-4-805a492124da@oss.qualcomm.com>

On Thu, Apr 09, 2026 at 01:52:27AM -0700, Jingyi Wang wrote:
> Subsystems can be brought out of reset by entities such as bootloaders.
> As the irq enablement could be later than subsystem bring up, the state
> of subsystem should be checked by reading SMP2P bits and performing ping
> test.
> 

I still don't understand.

Are you saying that devm_request_threaded_irq() will succeed and then
calling irq_get_irqchip_state() will not work? Or are you saying that
SMP2P driver isn't reliable and we're loosing the ready or fatal bits?


In the reply to v4 you replied to me with "it's a downstream feature".
That isn't a reason for performing this extra dance, either downstream
or upstream.

> A new qcom_pas_attach() function is introduced. if a crash state is
> detected for the subsystem, rproc_report_crash() is called. If the
> subsystem is ready and the ping is successful, it will be marked as
> "attached". If ready irq is not received, it could be the early boot
> feature is not supported by other entities. In this case, the state will
> be marked as RPROC_OFFLINE so that the PAS driver can load the firmware
> and start the remoteproc.
> 
> Co-developed-by: Gokul Krishna Krishnakumar <gokul.krishnakumar@oss.qualcomm.com>
> Signed-off-by: Gokul Krishna Krishnakumar <gokul.krishnakumar@oss.qualcomm.com>
> Signed-off-by: Jingyi Wang <jingyi.wang@oss.qualcomm.com>
> ---
>  drivers/remoteproc/qcom_q6v5.c     | 69 ++++++++++++++++++++++++++++++++
>  drivers/remoteproc/qcom_q6v5.h     |  6 +++
>  drivers/remoteproc/qcom_q6v5_pas.c | 80 ++++++++++++++++++++++++++++++++++++--
>  3 files changed, 152 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/remoteproc/qcom_q6v5.c b/drivers/remoteproc/qcom_q6v5.c
> index 58d5b85e58cd..52247c17c38a 100644
> --- a/drivers/remoteproc/qcom_q6v5.c
> +++ b/drivers/remoteproc/qcom_q6v5.c
> @@ -20,6 +20,7 @@
>  
>  #define Q6V5_LOAD_STATE_MSG_LEN	64
>  #define Q6V5_PANIC_DELAY_MS	200
> +#define Q6V5_PING_TIMEOUT_MS	500

Changelog says you removed 5 second timeout, but you only removed 4.5
seconds.

Regards,
Bjorn

>  
>  static int q6v5_load_state_toggle(struct qcom_q6v5 *q6v5, bool enable)
>  {
> @@ -234,6 +235,74 @@ unsigned long qcom_q6v5_panic(struct qcom_q6v5 *q6v5)
>  }
>  EXPORT_SYMBOL_GPL(qcom_q6v5_panic);
>  
> +static irqreturn_t q6v5_pong_interrupt(int irq, void *data)
> +{
> +	struct qcom_q6v5 *q6v5 = data;
> +
> +	complete(&q6v5->ping_done);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +int qcom_q6v5_ping_subsystem(struct qcom_q6v5 *q6v5)
> +{
> +	int ret;
> +	int ping_failed = 0;
> +
> +	reinit_completion(&q6v5->ping_done);
> +
> +	/* Set master kernel Ping bit */
> +	ret = qcom_smem_state_update_bits(q6v5->ping_state,
> +					  BIT(q6v5->ping_bit), BIT(q6v5->ping_bit));
> +	if (ret) {
> +		dev_err(q6v5->dev, "Failed to update ping bits\n");
> +		return ret;
> +	}
> +
> +	ret = wait_for_completion_timeout(&q6v5->ping_done, msecs_to_jiffies(Q6V5_PING_TIMEOUT_MS));
> +	if (!ret) {
> +		ping_failed = -ETIMEDOUT;
> +		dev_err(q6v5->dev, "Failed to get back pong\n");
> +	}
> +
> +	/* Clear ping bit master kernel */
> +	ret = qcom_smem_state_update_bits(q6v5->ping_state, BIT(q6v5->ping_bit), 0);
> +	if (ret) {
> +		dev_err(q6v5->dev, "Failed to clear master kernel bits\n");
> +		return ret;
> +	}
> +
> +	return ping_failed;
> +}
> +EXPORT_SYMBOL_GPL(qcom_q6v5_ping_subsystem);
> +
> +int qcom_q6v5_ping_subsystem_init(struct qcom_q6v5 *q6v5, struct platform_device *pdev)
> +{
> +	int ret = -ENODEV;
> +
> +	q6v5->ping_state = devm_qcom_smem_state_get(&pdev->dev, "ping", &q6v5->ping_bit);
> +	if (IS_ERR(q6v5->ping_state)) {
> +		dev_err(&pdev->dev, "Failed to acquire smem state %ld\n",
> +			PTR_ERR(q6v5->ping_state));
> +		return PTR_ERR(q6v5->ping_state);
> +	}
> +
> +	init_completion(&q6v5->ping_done);
> +
> +	q6v5->pong_irq = platform_get_irq_byname(pdev, "pong");
> +	if (q6v5->pong_irq < 0)
> +		return q6v5->pong_irq;
> +
> +	ret = devm_request_threaded_irq(&pdev->dev, q6v5->pong_irq, NULL,
> +					q6v5_pong_interrupt, IRQF_TRIGGER_RISING | IRQF_ONESHOT,
> +					"q6v5 pong", q6v5);
> +	if (ret)
> +		dev_err(&pdev->dev, "Failed to acquire pong IRQ\n");
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(qcom_q6v5_ping_subsystem_init);
> +
>  /**
>   * qcom_q6v5_init() - initializer of the q6v5 common struct
>   * @q6v5:	handle to be initialized
> diff --git a/drivers/remoteproc/qcom_q6v5.h b/drivers/remoteproc/qcom_q6v5.h
> index 5a859c41896e..5025ffc4dbe8 100644
> --- a/drivers/remoteproc/qcom_q6v5.h
> +++ b/drivers/remoteproc/qcom_q6v5.h
> @@ -17,22 +17,26 @@ struct qcom_q6v5 {
>  	struct rproc *rproc;
>  
>  	struct qcom_smem_state *state;
> +	struct qcom_smem_state *ping_state;
>  	struct qmp *qmp;
>  
>  	struct icc_path *path;
>  
>  	unsigned stop_bit;
> +	unsigned int ping_bit;
>  
>  	int wdog_irq;
>  	int fatal_irq;
>  	int ready_irq;
>  	int handover_irq;
>  	int stop_irq;
> +	int pong_irq;
>  
>  	bool handover_issued;
>  
>  	struct completion start_done;
>  	struct completion stop_done;
> +	struct completion ping_done;
>  
>  	int crash_reason;
>  
> @@ -52,5 +56,7 @@ int qcom_q6v5_unprepare(struct qcom_q6v5 *q6v5);
>  int qcom_q6v5_request_stop(struct qcom_q6v5 *q6v5, struct qcom_sysmon *sysmon);
>  int qcom_q6v5_wait_for_start(struct qcom_q6v5 *q6v5, int timeout);
>  unsigned long qcom_q6v5_panic(struct qcom_q6v5 *q6v5);
> +int qcom_q6v5_ping_subsystem(struct qcom_q6v5 *q6v5);
> +int qcom_q6v5_ping_subsystem_init(struct qcom_q6v5 *q6v5, struct platform_device *pdev);
>  
>  #endif
> diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c
> index da27d1d3c9da..34b54cf832d0 100644
> --- a/drivers/remoteproc/qcom_q6v5_pas.c
> +++ b/drivers/remoteproc/qcom_q6v5_pas.c
> @@ -60,6 +60,7 @@ struct qcom_pas_data {
>  	int region_assign_count;
>  	bool region_assign_shared;
>  	int region_assign_vmid;
> +	bool early_boot;
>  };
>  
>  struct qcom_pas {
> @@ -423,9 +424,15 @@ static int qcom_pas_stop(struct rproc *rproc)
>  
>  	qcom_pas_unmap_carveout(rproc, pas->mem_phys, pas->mem_size);
>  
> -	handover = qcom_q6v5_unprepare(&pas->q6v5);
> -	if (handover)
> -		qcom_pas_handover(&pas->q6v5);
> +	/*
> +	 * qcom_q6v5_prepare is not called in qcom_pas_attach, skip unprepare to
> +	 * avoid mismatch.
> +	 */
> +	if (pas->rproc->state != RPROC_ATTACHED) {
> +		handover = qcom_q6v5_unprepare(&pas->q6v5);
> +		if (handover)
> +			qcom_pas_handover(&pas->q6v5);
> +	}
>  
>  	if (pas->smem_host_id)
>  		ret = qcom_smem_bust_hwspin_lock_by_host(pas->smem_host_id);
> @@ -510,6 +517,63 @@ static unsigned long qcom_pas_panic(struct rproc *rproc)
>  	return qcom_q6v5_panic(&pas->q6v5);
>  }
>  
> +static int qcom_pas_attach(struct rproc *rproc)
> +{
> +	int ret;
> +	struct qcom_pas *pas = rproc->priv;
> +	bool ready_state;
> +	bool crash_state;
> +
> +	pas->q6v5.running = true;
> +	ret = irq_get_irqchip_state(pas->q6v5.fatal_irq,
> +				    IRQCHIP_STATE_LINE_LEVEL, &crash_state);
> +
> +	if (ret)
> +		goto disable_running;
> +
> +	if (crash_state) {
> +		dev_err(pas->dev, "Subsystem has crashed before driver probe\n");
> +		rproc_report_crash(rproc, RPROC_FATAL_ERROR);
> +		ret = -EINVAL;
> +		goto disable_running;
> +	}
> +
> +	ret = irq_get_irqchip_state(pas->q6v5.ready_irq,
> +				    IRQCHIP_STATE_LINE_LEVEL, &ready_state);
> +
> +	if (ret)
> +		goto disable_running;
> +
> +	if (unlikely(!ready_state)) {
> +		/*
> +		 * The bootloader may not support early boot, mark the state as
> +		 * RPROC_OFFLINE so that the PAS driver can load the firmware and
> +		 * start the remoteproc.
> +		 */
> +		dev_err(pas->dev, "Failed to get subsystem ready interrupt\n");
> +		pas->rproc->state = RPROC_OFFLINE;
> +		ret = -EINVAL;
> +		goto disable_running;
> +	}
> +
> +	ret = qcom_q6v5_ping_subsystem(&pas->q6v5);
> +
> +	if (ret) {
> +		dev_err(pas->dev, "Failed to ping subsystem, assuming device crashed\n");
> +		rproc_report_crash(rproc, RPROC_FATAL_ERROR);
> +		goto disable_running;
> +	}
> +
> +	pas->q6v5.handover_issued = true;
> +
> +	return 0;
> +
> +disable_running:
> +	pas->q6v5.running = false;
> +
> +	return ret;
> +}
> +
>  static const struct rproc_ops qcom_pas_ops = {
>  	.unprepare = qcom_pas_unprepare,
>  	.start = qcom_pas_start,
> @@ -518,6 +582,7 @@ static const struct rproc_ops qcom_pas_ops = {
>  	.parse_fw = qcom_pas_parse_firmware,
>  	.load = qcom_pas_load,
>  	.panic = qcom_pas_panic,
> +	.attach = qcom_pas_attach,
>  };
>  
>  static const struct rproc_ops qcom_pas_minidump_ops = {
> @@ -855,6 +920,15 @@ static int qcom_pas_probe(struct platform_device *pdev)
>  
>  	pas->pas_ctx->use_tzmem = rproc->has_iommu;
>  	pas->dtb_pas_ctx->use_tzmem = rproc->has_iommu;
> +
> +	if (desc->early_boot) {
> +		ret = qcom_q6v5_ping_subsystem_init(&pas->q6v5, pdev);
> +		if (ret)
> +			dev_warn(&pdev->dev, "Falling back to firmware load\n");
> +		else
> +			pas->rproc->state = RPROC_DETACHED;
> +	}
> +
>  	ret = rproc_add(rproc);
>  	if (ret)
>  		goto remove_ssr_sysmon;
> 
> -- 
> 2.34.1
> 

^ permalink raw reply

* Re: [PATCH v3] Add remoteproc PAS loader for SoCCP on Glymur DT
From: Bjorn Andersson @ 2026-04-11  2:52 UTC (permalink / raw)
  To: Ananthu C V
  Cc: Konrad Dybcio, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	linux-arm-msm, devicetree, linux-kernel, Sibi Sankar
In-Reply-To: <20260403-glymur-soccp-v3-1-f0e8d57f11ba@oss.qualcomm.com>

On Fri, Apr 03, 2026 at 04:39:05AM -0700, Ananthu C V wrote:
> From: Sibi Sankar <sibi.sankar@oss.qualcomm.com>
> 

Your commit is lacking both subject prefix and commit message.

> Signed-off-by: Sibi Sankar <sibi.sankar@oss.qualcomm.com>
> Co-developed-by: Ananthu C V <ananthu.cv@oss.qualcomm.com>
> Signed-off-by: Ananthu C V <ananthu.cv@oss.qualcomm.com>
> ---

v3? Where is the changelog?

>  arch/arm64/boot/dts/qcom/glymur-crd.dtsi |  7 +++++
>  arch/arm64/boot/dts/qcom/glymur.dtsi     | 47 ++++++++++++++++++++++++++++++++
>  2 files changed, 54 insertions(+)
> 
> diff --git a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi
> index 2852d257ac8c..3fdf8dbbde02 100644
> --- a/arch/arm64/boot/dts/qcom/glymur-crd.dtsi
> +++ b/arch/arm64/boot/dts/qcom/glymur-crd.dtsi
> @@ -560,6 +560,13 @@ &pon_resin {
>  	status = "okay";
>  };
>  
> +&remoteproc_soccp {
> +	firmware-name = "qcom/glymur/soccp.mbn",
> +			"qcom/glymur/soccp_dtb.mbn";
> +
> +	status = "okay";
> +};
> +
>  &tlmm {
>  	gpio-reserved-ranges = <4 4>, /* EC TZ Secure I3C */
>  			       <10 2>, /* OOB UART */
> diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi
> index f23cf81ddb77..f7f3374a5e08 100644
> --- a/arch/arm64/boot/dts/qcom/glymur.dtsi
> +++ b/arch/arm64/boot/dts/qcom/glymur.dtsi
> @@ -2264,6 +2264,53 @@ &config_noc SLAVE_QUP_0 QCOM_ICC_TAG_ALWAYS>,
>  			};
>  		};
>  
> +		remoteproc_soccp: remoteproc-soccp@d00000 {

Isn't remoteproc@ sufficient?

> +			compatible = "qcom,glymur-soccp-pas", "qcom,kaanapali-soccp-pas";

This binding hasn't been merged, and yet you don't mention that this
can't be merged?

> +			reg = <0x0 0x00d00000 0x0 0x200000>;
> +
> +			interrupts-extended = <&intc GIC_SPI 167 IRQ_TYPE_EDGE_RISING>,
> +					      <&soccp_smp2p_in 0 IRQ_TYPE_EDGE_RISING>,
> +					      <&soccp_smp2p_in 1 IRQ_TYPE_EDGE_RISING>,
> +					      <&soccp_smp2p_in 2 IRQ_TYPE_EDGE_RISING>,
> +					      <&soccp_smp2p_in 3 IRQ_TYPE_EDGE_RISING>,
> +					      <&soccp_smp2p_in 9 IRQ_TYPE_EDGE_RISING>;
> +			interrupt-names = "wdog",
> +					  "fatal",
> +					  "ready",
> +					  "handover",
> +					  "stop-ack",
> +					  "pong";
> +
> +			clocks = <&rpmhcc RPMH_CXO_CLK>;
> +			clock-names = "xo";
> +
> +			power-domains = <&rpmhpd RPMHPD_CX>,
> +					<&rpmhpd RPMHPD_MX>;
> +			power-domain-names = "cx",
> +					     "mx";
> +
> +			memory-region = <&soccp_mem>,
> +					<&soccpdtb_mem>;
> +
> +			qcom,smem-states = <&soccp_smp2p_out 0>,
> +					   <&soccp_smp2p_out 8>;
> +			qcom,smem-state-names = "stop",
> +						"ping";
> +
> +			status = "disabled";
> +
> +			glink-edge {
> +				interrupts-extended = <&ipcc IPCC_MPROC_SOCCP
> +							     IPCC_MPROC_SIGNAL_GLINK_QMP
> +							     IRQ_TYPE_EDGE_RISING>;
> +				mboxes = <&ipcc IPCC_MPROC_SOCCP
> +						IPCC_MPROC_SIGNAL_GLINK_QMP>;
> +				qcom,remote-pid = <19>;
> +				label = "soccp";
> +
> +			};
> +		};
> +
>  		usb_hs_phy: phy@fa0000 {
>  			compatible = "qcom,glymur-m31-eusb2-phy",
>  				     "qcom,sm8750-m31-eusb2-phy";
> 
> ---
> base-commit: bd0f139e5fc11182777b81cefc3893ea508544ec
> change-id: 20260403-glymur-soccp-2ca25f3b30e2
> prerequisite-message-id: <20260326-knp-soccp-dt-v1-0-a60c2ae36e9b@oss.qualcomm.com>
> prerequisite-patch-id: fa390011ee531589a7ad14250d158f497622efbd
> prerequisite-patch-id: 93e7fca58a5c06edefa624ec2b006dd80f4749a8
> prerequisite-patch-id: 99a3b6a7fcd061267b40097ad25f652ebe0a4c7b

Why isn't this list empty?

Regards,
Bjorn

> 
> Best regards,
> --  
> Ananthu C V <ananthu.cv@oss.qualcomm.com>
> 

^ permalink raw reply

* Re: [PATCH 00/35] irqchip/qcom-pdc: Clean up register mapping and DT descriptions
From: Bjorn Andersson @ 2026-04-11  2:48 UTC (permalink / raw)
  To: Mukesh Ojha
  Cc: Thomas Gleixner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Konrad Dybcio, cros-qcom-dts-watchers, linux-arm-msm,
	linux-kernel, devicetree
In-Reply-To: <20260410184124.1068210-1-mukesh.ojha@oss.qualcomm.com>

On Sat, Apr 11, 2026 at 12:10:37AM +0530, Mukesh Ojha wrote:
> The Qualcomm PDC (Power Domain Controller) hardware exposes multiple DRV
> (Driver) regions, each 0x10000 bytes in size, where each region serves a
> specific client in the system . Linux only needs access to the APSS DRV
> region.
> 
> Despite this, the driver was mapping up to 0x30000 bytes (three DRV
> regions) via a QCOM_PDC_SIZE clamp introduced as a workaround for old
> sm8150 DTs that described a too-small register window. Correspondingly,
> most platform DTS files described the PDC reg as 0x30000 in size, and
> several also carried a second, entirely unused reg entry pointing at an
> unrelated register region that the driver never maps.
> 
> This series cleans all of that up in three logical steps:
> 
> 1. (patches 2-6):
> 

These patches are for the IRQ subsystem/maintainer.

> Split __pdc_enable_intr() into two focused per-version helpers
> to separate the HW < 3.2 bank-based path from the HW >= 3.2 per-pin
> path. Replace the pdc_version global with a function pointer assigned
> once at probe time, moving the version check out of the hot path.
> Tighten the ioremap clamp from QCOM_PDC_SIZE (0x30000) to PDC_DRV_SIZE
> (0x10000) now that the DT fixes below make the workaround unnecessary.
> Also add a PDC_VERSION() constructor macro and use FIELD_GET() for bank
> index extraction to make the bit encoding self-documenting.
> 
> 2. (patches 1, 7-28):
> 

And these patches are for the Qualcomm SoC/DT tree.

> All 28 platform DTS files that described the PDC reg window as 0x30000
> are corrected to 0x10000, reflecting the single APSS DRV region that
> Linux actually maps.
> 
> 3. (patches 29-35):
> 

Same with these.


I don't see any dependencies between the IRQ and DT patches, can they be
merged independently? Why did you send them together?

Regards,
Bjorn

> Seven platform DTS files (kaanapali, lemans, milos, monaco, sc8280xp,
> sdx75, talos) carried a second reg entry pointing at an unrelated
> hardware block. The driver only ever calls of_address_to_resource(node,
> 0, ...) so this second entry was never mapped or accessed. Remove it.
> 
> The net result is that every PDC node in the tree now describes exactly
> one register region of exactly 0x10000 bytes — the APSS DRV region that
> the driver actually uses — and the driver's ioremap clamp matches that
> reality.
> 
> Mukesh Ojha (35):
>   dt-bindings: qcom,pdc: Tighten reg to single APSS DRV region
>   irqchip/qcom-pdc: Split __pdc_enable_intr() into per-version helpers
>   irqchip/qcom-pdc: Tighten ioremap clamp to single DRV region size
>   irqchip/qcom-pdc: Replace pdc_version global with a function pointer
>   irqchip/qcom-pdc: Add PDC_VERSION() macro to describe version register
>     fields
>   irqchip/qcom-pdc: Use FIELD_GET() to extract bank index and bit
>     position
>   arm64: dts: qcom: sdm845: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: sdm670: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: sc7180: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: sc7280: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: sc8180x: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: sm8150: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: sc8280xp: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: sm8250: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: sm8350: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: sm8450: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: sm8550: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: sm8650: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: sm4450: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: x1e80100: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: sm6350: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: sar2130p: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: qcs615: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: qcs8300: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: sa8775p: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: sdx75: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: milos: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: qdu1000: Fix PDC reg size to single APSS DRV region
>   arm64: dts: qcom: kaanapali: Drop unused second PDC reg entry
>   arm64: dts: qcom: lemans: Drop unused second PDC reg entry
>   arm64: dts: qcom: milos: Drop unused second PDC reg entry
>   arm64: dts: qcom: monaco: Drop unused second PDC reg entry
>   arm64: dts: qcom: sc8280xp: Drop unused second PDC reg entry
>   arm64: dts: qcom: sdx75: Drop unused second PDC reg entry
>   arm64: dts: qcom: talos: Drop unused second PDC reg entry
> 
>  .../interrupt-controller/qcom,pdc.yaml        |  2 +-
>  arch/arm64/boot/dts/qcom/hamoa.dtsi           |  2 +-
>  arch/arm64/boot/dts/qcom/kaanapali.dtsi       |  3 +-
>  arch/arm64/boot/dts/qcom/kodiak.dtsi          |  2 +-
>  arch/arm64/boot/dts/qcom/lemans.dtsi          |  3 +-
>  arch/arm64/boot/dts/qcom/milos.dtsi           |  3 +-
>  arch/arm64/boot/dts/qcom/monaco.dtsi          |  3 +-
>  arch/arm64/boot/dts/qcom/qdu1000.dtsi         |  2 +-
>  arch/arm64/boot/dts/qcom/sar2130p.dtsi        |  2 +-
>  arch/arm64/boot/dts/qcom/sc7180.dtsi          |  2 +-
>  arch/arm64/boot/dts/qcom/sc8180x.dtsi         |  2 +-
>  arch/arm64/boot/dts/qcom/sc8280xp.dtsi        |  2 +-
>  arch/arm64/boot/dts/qcom/sdm670.dtsi          |  2 +-
>  arch/arm64/boot/dts/qcom/sdm845.dtsi          |  2 +-
>  arch/arm64/boot/dts/qcom/sdx75.dtsi           |  3 +-
>  arch/arm64/boot/dts/qcom/sm4450.dtsi          |  2 +-
>  arch/arm64/boot/dts/qcom/sm6350.dtsi          |  2 +-
>  arch/arm64/boot/dts/qcom/sm8150.dtsi          |  2 +-
>  arch/arm64/boot/dts/qcom/sm8250.dtsi          |  2 +-
>  arch/arm64/boot/dts/qcom/sm8350.dtsi          |  2 +-
>  arch/arm64/boot/dts/qcom/sm8450.dtsi          |  2 +-
>  arch/arm64/boot/dts/qcom/sm8550.dtsi          |  2 +-
>  arch/arm64/boot/dts/qcom/sm8650.dtsi          |  2 +-
>  arch/arm64/boot/dts/qcom/talos.dtsi           |  3 +-
>  drivers/irqchip/qcom-pdc.c                    | 56 +++++++++++--------
>  25 files changed, 57 insertions(+), 53 deletions(-)
> 
> -- 
> 2.53.0
> 

^ permalink raw reply

* [PATCH v6 3/3] arm64: dts: rockchip: Add Orange Pi 5 Pro board support
From: dennis @ 2026-04-11  2:47 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner
  Cc: FUKAUMI Naoki, Hsun Lai, Jonas Karlman, Chaoyi Chen, John Clark,
	Michael Opdenacker, Quentin Schulz, Andrew Lunn, Chukun Pan,
	Alexey Charkov, Peter Robinson, Dennis Gilmore, Michael Riesch,
	Mykola Kvach, Jimmy Hon, devicetree, linux-arm-kernel,
	linux-rockchip, linux-kernel
In-Reply-To: <20260411024743.195385-1-dennis@ausil.us>

From: Dennis Gilmore <dennis@ausil.us>

Add device tree for the Xunlong Orange Pi 5 Pro (RK3588S).

- eMMC module, you can optionally solder a SPI NOR in place and turn
 off the eMMC
- PCIe-attached NIC (pcie2x1l1)
- PCIe NVMe slot (pcie2x1l2)
- AP6256 WiFi (BCM43456) via SDIO with mmc-pwrseq
- BCM4345C5 Bluetooth
- es8388 audio
- USB 2.0 and USB 3.0
- Two HDMI ports, the second is connected to the SoC's DP controller
  driven by a transparent LT8711UXD bridge that has firmware onboard and
  needs no node defined.

Vendors description and links to schematics available:
http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/details/Orange-Pi-5-Pro.html

Signed-off-by: Dennis Gilmore <dennis@ausil.us>
---
 .../display/rockchip/rockchip,dw-dp.yaml      |   7 +
 arch/arm64/boot/dts/rockchip/Makefile         |   1 +
 .../dts/rockchip/rk3588s-orangepi-5-pro.dts   | 352 ++++++++++++++++++
 drivers/gpu/drm/bridge/synopsys/dw-dp.c       |  12 +
 4 files changed, 372 insertions(+)
 create mode 100644 arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5-pro.dts

diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
index 6345f0132d43..079a912d97f1 100644
--- a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
+++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
@@ -57,6 +57,13 @@ properties:
       - const: i2s
       - const: spdif
 
+  hpd-gpios:
+    maxItems: 1
+    description:
+      GPIO used for hot plug detection when the controller's native HPD
+      input is not connected. If not specified, the controller uses its
+      internal HPD detection mechanism.
+
   phys:
     maxItems: 1
 
diff --git a/arch/arm64/boot/dts/rockchip/Makefile b/arch/arm64/boot/dts/rockchip/Makefile
index 4d384f153c13..c99dca2ae9e7 100644
--- a/arch/arm64/boot/dts/rockchip/Makefile
+++ b/arch/arm64/boot/dts/rockchip/Makefile
@@ -214,6 +214,7 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-nanopi-r6c.dtb
 dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-odroid-m2.dtb
 dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-orangepi-5.dtb
 dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-orangepi-5b.dtb
+dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-orangepi-5-pro.dtb
 dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-orangepi-cm5-base.dtb
 dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-radxa-cm5-io.dtb
 dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-roc-pc.dtb
diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5-pro.dts b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5-pro.dts
new file mode 100644
index 000000000000..84c83aa69f63
--- /dev/null
+++ b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5-pro.dts
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+
+/dts-v1/;
+
+#include "rk3588s-orangepi-5.dtsi"
+
+/ {
+	model = "Xunlong Orange Pi 5 Pro";
+	compatible = "xunlong,orangepi-5-pro", "rockchip,rk3588s";
+
+	aliases {
+		mmc0 = &sdhci;
+		mmc1 = &sdmmc;
+		mmc2 = &sdio;
+	};
+
+	dp-con {
+		compatible = "dp-connector";
+
+		port {
+			dp_con_in: endpoint {
+				remote-endpoint = <&dp0_out_con>;
+			};
+		};
+	};
+
+	analog-sound {
+		compatible = "simple-audio-card";
+		pinctrl-names = "default";
+		pinctrl-0 = <&hp_detect>;
+		simple-audio-card,bitclock-master = <&masterdai>;
+		simple-audio-card,format = "i2s";
+		simple-audio-card,frame-master = <&masterdai>;
+		simple-audio-card,hp-det-gpios = <&gpio1 RK_PD5 GPIO_ACTIVE_HIGH>;
+		simple-audio-card,mclk-fs = <256>;
+		simple-audio-card,name = "rockchip,es8388";
+		simple-audio-card,routing =
+			"Headphones", "LOUT1",
+			"Headphones", "ROUT1",
+			"LINPUT1", "Microphone Jack",
+			"RINPUT1", "Microphone Jack",
+			"LINPUT2", "Onboard Microphone",
+			"RINPUT2", "Onboard Microphone";
+		simple-audio-card,widgets =
+			"Microphone", "Microphone Jack",
+			"Microphone", "Onboard Microphone",
+			"Headphone", "Headphones";
+
+		simple-audio-card,cpu {
+			sound-dai = <&i2s2_2ch>;
+		};
+
+		masterdai: simple-audio-card,codec {
+			sound-dai = <&es8388>;
+			system-clock-frequency = <12288000>;
+		};
+	};
+
+	pwm-leds {
+		compatible = "pwm-leds";
+
+		led-0 {
+			color = <LED_COLOR_ID_BLUE>;
+			function = LED_FUNCTION_STATUS;
+			linux,default-trigger = "heartbeat";
+			max-brightness = <255>;
+			pwms = <&pwm15 0 1000000 0>;
+		};
+
+		led-1 {
+			color = <LED_COLOR_ID_GREEN>;
+			function = LED_FUNCTION_ACTIVITY;
+			linux,default-trigger = "heartbeat";
+			max-brightness = <255>;
+			pwms = <&pwm3 0 1000000 0>;
+		};
+	};
+
+	fan: pwm-fan {
+		compatible = "pwm-fan";
+		#cooling-cells = <2>;
+		cooling-levels = <0 50 100 150 200 255>;
+		fan-supply = <&vcc5v0_sys>;
+		pwms = <&pwm2 0 20000000 0>;
+	};
+
+	vcc3v3_dp: regulator-vcc3v3-dp {
+		compatible = "regulator-fixed";
+		enable-active-high;
+		gpios = <&gpio3 RK_PC2 GPIO_ACTIVE_HIGH>;
+		regulator-always-on;
+		regulator-boot-on;
+		regulator-max-microvolt = <3300000>;
+		regulator-min-microvolt = <3300000>;
+		regulator-name = "vcc3v3_dp";
+		vin-supply = <&vcc_3v3_s3>;
+	};
+
+	vcc3v3_phy1: regulator-vcc3v3-phy1 {
+		compatible = "regulator-fixed";
+		enable-active-high;
+		gpios = <&gpio3 RK_PB7 GPIO_ACTIVE_HIGH>;
+		regulator-boot-on;
+		regulator-max-microvolt = <3300000>;
+		regulator-min-microvolt = <3300000>;
+		regulator-name = "vcc3v3_phy1";
+		startup-delay-us = <50000>;
+		vin-supply = <&vcc_3v3_s3>;
+	};
+
+	vcc5v0_otg: regulator-vcc5v0-otg {
+		compatible = "regulator-fixed";
+		enable-active-high;
+		gpios = <&gpio0 RK_PC4 GPIO_ACTIVE_HIGH>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&vcc5v0_otg_en>;
+		regulator-max-microvolt = <5000000>;
+		regulator-min-microvolt = <5000000>;
+		regulator-name = "vcc5v0_otg";
+		vin-supply = <&vcc5v0_sys>;
+	};
+
+	sdio_pwrseq: sdio-pwrseq {
+		compatible = "mmc-pwrseq-simple";
+		clocks = <&hym8563>;
+		clock-names = "ext_clock";
+		post-power-on-delay-ms = <200>;
+		reset-gpios = <&gpio0 RK_PD0 GPIO_ACTIVE_LOW>;
+	};
+
+	typea_con: usb-a-connector {
+		compatible = "usb-a-connector";
+		data-role = "host";
+		label = "USB3 Type-A";
+		power-role = "source";
+		vbus-supply = <&vcc5v0_otg>;
+	};
+};
+
+&dp0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&dp0m0_pins>;
+	status = "okay";
+};
+
+&dp0_in {
+	dp0_in_vp1: endpoint {
+		remote-endpoint = <&vp1_out_dp0>;
+	};
+};
+
+&dp0_out {
+	dp0_out_con: endpoint {
+		remote-endpoint = <&dp_con_in>;
+	};
+};
+
+&i2c1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1m4_xfer>;
+	status = "okay";
+};
+
+&i2c3 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c3m0_xfer>;
+	status = "okay";
+
+	es8388: audio-codec@11 {
+		compatible = "everest,es8388", "everest,es8328";
+		reg = <0x11>;
+		#sound-dai-cells = <0>;
+		AVDD-supply = <&vcc_3v3_s0>;
+		DVDD-supply = <&vcc_1v8_s0>;
+		HPVDD-supply = <&vcc_3v3_s0>;
+		PVDD-supply = <&vcc_3v3_s0>;
+		assigned-clock-rates = <12288000>;
+		assigned-clocks = <&cru I2S2_2CH_MCLKOUT>;
+		clocks = <&cru I2S2_2CH_MCLKOUT>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&i2s2m1_mclk>;
+	};
+};
+
+&i2c4 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c4m3_xfer>;
+	status = "okay";
+};
+
+&i2s2_2ch {
+	pinctrl-0 = <&i2s2m1_lrck &i2s2m1_sclk
+		     &i2s2m1_sdi &i2s2m1_sdo>;
+	status = "okay";
+};
+
+&package_thermal {
+	polling-delay = <1000>;
+
+	cooling-maps {
+		map0 {
+			trip = <&package_fan0>;
+			cooling-device = <&fan THERMAL_NO_LIMIT 1>;
+		};
+
+		map1 {
+			trip = <&package_fan1>;
+			cooling-device = <&fan 2 THERMAL_NO_LIMIT>;
+		};
+	};
+
+	trips {
+		package_fan0: package-fan0 {
+			hysteresis = <2000>;
+			temperature = <55000>;
+			type = "active";
+		};
+
+		package_fan1: package-fan1 {
+			hysteresis = <2000>;
+			temperature = <65000>;
+			type = "active";
+		};
+	};
+};
+
+/* NVMe */
+&pcie2x1l1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&pcie30x1m1_1_clkreqn &pcie30x1m1_1_waken>;
+	reset-gpios = <&gpio4 RK_PA2 GPIO_ACTIVE_HIGH>;
+	supports-clkreq;
+	vpcie3v3-supply = <&vcc_3v3_s3>;
+	status = "okay";
+};
+
+/* NIC */
+&pcie2x1l2 {
+	reset-gpios = <&gpio3 RK_PD1 GPIO_ACTIVE_HIGH>;
+	vpcie3v3-supply = <&vcc3v3_phy1>;
+	status = "okay";
+};
+
+&pinctrl {
+	bluetooth {
+		bt_wake_gpio: bt-wake-pin {
+			rockchip,pins = <0 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>;
+		};
+
+		bt_wake_host_irq: bt-wake-host-irq {
+			rockchip,pins = <0 RK_PC5 RK_FUNC_GPIO &pcfg_pull_down>;
+		};
+	};
+
+	usb {
+		vcc5v0_otg_en: vcc5v0-otg-en {
+			rockchip,pins = <0 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>;
+		};
+	};
+
+	wlan {
+		wifi_host_wake_irq: wifi-host-wake-irq {
+			rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_down>;
+		};
+	};
+};
+
+&pwm15 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&pwm15m2_pins>;
+	status = "okay";
+};
+
+&pwm2 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&pwm2m1_pins>;
+	status = "okay";
+};
+
+&pwm3 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&pwm3m2_pins>;
+	status = "okay";
+};
+
+&sdhci {
+	status = "okay";
+};
+
+&sdio {
+	#address-cells = <1>;
+	#size-cells = <0>;
+	bus-width = <4>;
+	cap-sd-highspeed;
+	cap-sdio-irq;
+	keep-power-in-suspend;
+	max-frequency = <150000000>;
+	mmc-pwrseq = <&sdio_pwrseq>;
+	no-mmc;
+	no-sd;
+	non-removable;
+	sd-uhs-sdr104;
+	status = "okay";
+
+	ap6256: wifi@1 {
+		compatible = "brcm,bcm43456-fmac", "brcm,bcm4329-fmac";
+		reg = <1>;
+		interrupt-names = "host-wake";
+		interrupt-parent = <&gpio0>;
+		interrupts = <RK_PA0 IRQ_TYPE_LEVEL_HIGH>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&wifi_host_wake_irq>;
+	};
+};
+
+&uart9 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&uart9m2_xfer &uart9m2_ctsn &uart9m2_rtsn>;
+	uart-has-rtscts;
+	status = "okay";
+
+	bluetooth {
+		compatible = "brcm,bcm4345c5";
+		clocks = <&hym8563>;
+		clock-names = "lpo";
+		device-wakeup-gpios = <&gpio0 RK_PC6 GPIO_ACTIVE_HIGH>;
+		interrupt-names = "host-wakeup";
+		interrupt-parent = <&gpio0>;
+		interrupts = <RK_PC5 IRQ_TYPE_LEVEL_HIGH>;
+		max-speed = <1500000>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&bt_wake_host_irq &bt_wake_gpio>;
+		shutdown-gpios = <&gpio0 RK_PD5 GPIO_ACTIVE_HIGH>;
+		vbat-supply = <&vcc_3v3_s3>;
+		vddio-supply = <&vcc_1v8_s3>;
+	};
+};
+
+&usb_host0_xhci {
+	dr_mode = "host";
+};
+
+&usbdp_phy0 {
+	rockchip,dp-lane-mux = <0 1>;
+};
+
+&vp1 {
+	vp1_out_dp0: endpoint@a {
+		reg = <ROCKCHIP_VOP2_EP_DP0>;
+		remote-endpoint = <&dp0_in_vp1>;
+	};
+};
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index fd23ca2834b0..b58f57b69b22 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -8,6 +8,7 @@
  */
 #include <linux/bitfield.h>
 #include <linux/clk.h>
+#include <linux/gpio/consumer.h>
 #include <linux/iopoll.h>
 #include <linux/irq.h>
 #include <linux/media-bus-format.h>
@@ -330,6 +331,8 @@ struct dw_dp {
 	u8 pixel_mode;
 
 	DECLARE_BITMAP(sdp_reg_bank, SDP_REG_BANK_SIZE);
+
+	struct gpio_desc *hpd_gpiod;
 };
 
 enum {
@@ -481,6 +484,9 @@ static bool dw_dp_hpd_detect(struct dw_dp *dp)
 {
 	u32 value;
 
+	if (dp->hpd_gpiod)
+		return gpiod_get_value_cansleep(dp->hpd_gpiod);
+
 	regmap_read(dp->regmap, DW_DP_HPD_STATUS, &value);
 
 	return FIELD_GET(HPD_STATE, value) == DW_DP_HPD_STATE_PLUG;
@@ -2002,6 +2008,12 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
 		return ERR_CAST(dp->regmap);
 	}
 
+	dp->hpd_gpiod = devm_gpiod_get_optional(dev, "hpd", GPIOD_IN);
+	if (IS_ERR(dp->hpd_gpiod)) {
+		dev_err_probe(dev, PTR_ERR(dp->hpd_gpiod), "failed to get hpd GPIO\n");
+		return ERR_CAST(dp->hpd_gpiod);
+	}
+
 	dp->phy = devm_of_phy_get(dev, dev->of_node, NULL);
 	if (IS_ERR(dp->phy)) {
 		dev_err_probe(dev, PTR_ERR(dp->phy), "failed to get phy\n");
-- 
2.53.0


^ permalink raw reply related

* [PATCH v6 2/3] arm64: dts: rockchip: refactor items from Orange Pi 5/b to prep for Pro
From: dennis @ 2026-04-11  2:47 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner
  Cc: FUKAUMI Naoki, Hsun Lai, Jonas Karlman, Chaoyi Chen, John Clark,
	Michael Opdenacker, Quentin Schulz, Andrew Lunn, Chukun Pan,
	Alexey Charkov, Peter Robinson, Dennis Gilmore, Michael Riesch,
	Mykola Kvach, Jimmy Hon, devicetree, linux-arm-kernel,
	linux-rockchip, linux-kernel
In-Reply-To: <20260411024743.195385-1-dennis@ausil.us>

From: Dennis Gilmore <dennis@ausil.us>

The Orange Pi 5 Pro uses the same SoC and base as the Orange Pi 5 and
Orange Pi 5B but has had sound, USB, and leds wired up differently. The
boards also use gmac for ethernet where the Pro has a PCIe attached NIC.

I have not changed the definitions from what was in rk3588s-orangepi-5.dtsi

Signed-off-by: Dennis Gilmore <dennis@ausil.us>
---
 .../dts/rockchip/rk3588s-orangepi-5-5b.dtsi   | 192 +++++++++++++++++
 .../boot/dts/rockchip/rk3588s-orangepi-5.dts  |   6 +-
 .../boot/dts/rockchip/rk3588s-orangepi-5.dtsi | 198 +-----------------
 .../boot/dts/rockchip/rk3588s-orangepi-5b.dts |   2 +-
 4 files changed, 209 insertions(+), 189 deletions(-)
 create mode 100644 arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5-5b.dtsi

diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5-5b.dtsi b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5-5b.dtsi
new file mode 100644
index 000000000000..b04dd667605d
--- /dev/null
+++ b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5-5b.dtsi
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Device tree definitions shared by the Orange Pi 5 and Orange Pi 5B
+ * but not the Orange Pi 5 Pro.
+ */
+
+#include <dt-bindings/usb/pd.h>
+#include "rk3588s-orangepi-5.dtsi"
+
+/ {
+	aliases {
+		ethernet0 = &gmac1;
+	};
+
+	analog-sound {
+		compatible = "simple-audio-card";
+		pinctrl-names = "default";
+		pinctrl-0 = <&hp_detect>;
+		simple-audio-card,name = "rockchip,es8388";
+		simple-audio-card,bitclock-master = <&masterdai>;
+		simple-audio-card,format = "i2s";
+		simple-audio-card,frame-master = <&masterdai>;
+		simple-audio-card,hp-det-gpios = <&gpio1 RK_PD5 GPIO_ACTIVE_HIGH>;
+		simple-audio-card,mclk-fs = <256>;
+		simple-audio-card,routing =
+			"Headphones", "LOUT1",
+			"Headphones", "ROUT1",
+			"LINPUT1", "Microphone Jack",
+			"RINPUT1", "Microphone Jack",
+			"LINPUT2", "Onboard Microphone",
+			"RINPUT2", "Onboard Microphone";
+		simple-audio-card,widgets =
+			"Microphone", "Microphone Jack",
+			"Microphone", "Onboard Microphone",
+			"Headphone", "Headphones";
+
+		simple-audio-card,cpu {
+			sound-dai = <&i2s1_8ch>;
+		};
+
+		masterdai: simple-audio-card,codec {
+			sound-dai = <&es8388>;
+			system-clock-frequency = <12288000>;
+		};
+	};
+
+	pwm-leds {
+		compatible = "pwm-leds";
+
+		led {
+			color = <LED_COLOR_ID_GREEN>;
+			function = LED_FUNCTION_STATUS;
+			linux,default-trigger = "heartbeat";
+			max-brightness = <255>;
+			pwms = <&pwm0 0 25000 0>;
+		};
+	};
+};
+
+&gmac1 {
+	clock_in_out = "output";
+	phy-handle = <&rgmii_phy1>;
+	phy-mode = "rgmii-rxid";
+	pinctrl-0 = <&gmac1_miim
+		     &gmac1_tx_bus2
+		     &gmac1_rx_bus2
+		     &gmac1_rgmii_clk
+		     &gmac1_rgmii_bus>;
+	pinctrl-names = "default";
+	tx_delay = <0x42>;
+	status = "okay";
+};
+
+&i2c6 {
+	es8388: audio-codec@10 {
+		compatible = "everest,es8388", "everest,es8328";
+		reg = <0x10>;
+		clocks = <&cru I2S1_8CH_MCLKOUT>;
+		AVDD-supply = <&vcc_3v3_s0>;
+		DVDD-supply = <&vcc_1v8_s0>;
+		HPVDD-supply = <&vcc_3v3_s0>;
+		PVDD-supply = <&vcc_3v3_s0>;
+		assigned-clocks = <&cru I2S1_8CH_MCLKOUT>;
+		assigned-clock-rates = <12288000>;
+		#sound-dai-cells = <0>;
+	};
+
+	usbc0: usb-typec@22 {
+		compatible = "fcs,fusb302";
+		reg = <0x22>;
+		interrupt-parent = <&gpio0>;
+		interrupts = <RK_PD3 IRQ_TYPE_LEVEL_LOW>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&usbc0_int>;
+		vbus-supply = <&vbus_typec>;
+		status = "okay";
+
+		usb_con: connector {
+			compatible = "usb-c-connector";
+			label = "USB-C";
+			data-role = "dual";
+			op-sink-microwatt = <1000000>;
+			power-role = "dual";
+			sink-pdos =
+				<PDO_FIXED(5000, 1000, PDO_FIXED_USB_COMM)>;
+			source-pdos =
+				<PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)>;
+			try-power-role = "source";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				port@0 {
+					reg = <0>;
+					usbc0_hs: endpoint {
+						remote-endpoint = <&usb_host0_xhci_drd_sw>;
+					};
+				};
+
+				port@1 {
+					reg = <1>;
+					usbc0_ss: endpoint {
+						remote-endpoint = <&usbdp_phy0_typec_ss>;
+					};
+				};
+
+				port@2 {
+					reg = <2>;
+					usbc0_sbu: endpoint {
+						remote-endpoint = <&usbdp_phy0_typec_sbu>;
+					};
+				};
+			};
+		};
+	};
+};
+
+&i2s1_8ch {
+	rockchip,i2s-tx-route = <3 2 1 0>;
+	rockchip,i2s-rx-route = <1 3 2 0>;
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2s1m0_sclk
+	             &i2s1m0_mclk
+	             &i2s1m0_lrck
+	             &i2s1m0_sdi1
+	             &i2s1m0_sdo3>;
+	status = "okay";
+};
+
+&pwm0 {
+	pinctrl-0 = <&pwm0m2_pins>;
+	pinctrl-names = "default";
+	status = "okay";
+};
+
+&usb_host0_xhci {
+	dr_mode = "otg";
+	usb-role-switch;
+
+	port {
+		usb_host0_xhci_drd_sw: endpoint {
+			remote-endpoint = <&usbc0_hs>;
+		};
+	};
+};
+
+&usb_host2_xhci {
+	status = "okay";
+};
+
+&usbdp_phy0 {
+	mode-switch;
+	orientation-switch;
+	sbu1-dc-gpios = <&gpio4 RK_PA5 GPIO_ACTIVE_HIGH>;
+	sbu2-dc-gpios = <&gpio4 RK_PA7 GPIO_ACTIVE_HIGH>;
+
+	port {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		usbdp_phy0_typec_ss: endpoint@0 {
+			reg = <0>;
+			remote-endpoint = <&usbc0_ss>;
+		};
+
+		usbdp_phy0_typec_sbu: endpoint@1 {
+			reg = <1>;
+			remote-endpoint = <&usbc0_sbu>;
+		};
+	};
+};
diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5.dts b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5.dts
index 83b9b6645a1e..d76bdf1b5e90 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5.dts
@@ -2,12 +2,16 @@
 
 /dts-v1/;
 
-#include "rk3588s-orangepi-5.dtsi"
+#include "rk3588s-orangepi-5-5b.dtsi"
 
 / {
 	model = "Xunlong Orange Pi 5";
 	compatible = "xunlong,orangepi-5", "rockchip,rk3588s";
 
+	aliases {
+		mmc0 = &sdmmc;
+	};
+
 	vcc3v3_pcie20: regulator-vcc3v3-pcie20 {
 		compatible = "regulator-fixed";
 		enable-active-high;
diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5.dtsi b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5.dtsi
index dafad29f9854..5c154cc6c62a 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5.dtsi
@@ -3,19 +3,13 @@
 /dts-v1/;
 
 #include <dt-bindings/gpio/gpio.h>
-#include <dt-bindings/leds/common.h>
 #include <dt-bindings/input/input.h>
+#include <dt-bindings/leds/common.h>
 #include <dt-bindings/pinctrl/rockchip.h>
 #include <dt-bindings/soc/rockchip,vop2.h>
-#include <dt-bindings/usb/pd.h>
 #include "rk3588s.dtsi"
 
 / {
-	aliases {
-		ethernet0 = &gmac1;
-		mmc0 = &sdmmc;
-	};
-
 	chosen {
 		stdout-path = "serial2:1500000n8";
 	};
@@ -34,38 +28,6 @@ button-recovery {
 		};
 	};
 
-	analog-sound {
-		compatible = "simple-audio-card";
-		pinctrl-names = "default";
-		pinctrl-0 = <&hp_detect>;
-		simple-audio-card,name = "rockchip,es8388";
-		simple-audio-card,bitclock-master = <&masterdai>;
-		simple-audio-card,format = "i2s";
-		simple-audio-card,frame-master = <&masterdai>;
-		simple-audio-card,hp-det-gpios = <&gpio1 RK_PD5 GPIO_ACTIVE_HIGH>;
-		simple-audio-card,mclk-fs = <256>;
-		simple-audio-card,routing =
-			"Headphones", "LOUT1",
-			"Headphones", "ROUT1",
-			"LINPUT1", "Microphone Jack",
-			"RINPUT1", "Microphone Jack",
-			"LINPUT2", "Onboard Microphone",
-			"RINPUT2", "Onboard Microphone";
-		simple-audio-card,widgets =
-			"Microphone", "Microphone Jack",
-			"Microphone", "Onboard Microphone",
-			"Headphone", "Headphones";
-
-		simple-audio-card,cpu {
-			sound-dai = <&i2s1_8ch>;
-		};
-
-		masterdai: simple-audio-card,codec {
-			sound-dai = <&es8388>;
-			system-clock-frequency = <12288000>;
-		};
-	};
-
 	hdmi0-con {
 		compatible = "hdmi-connector";
 		type = "a";
@@ -77,18 +39,6 @@ hdmi0_con_in: endpoint {
 		};
 	};
 
-	pwm-leds {
-		compatible = "pwm-leds";
-
-		led {
-			color = <LED_COLOR_ID_GREEN>;
-			function = LED_FUNCTION_STATUS;
-			linux,default-trigger = "heartbeat";
-			max-brightness = <255>;
-			pwms = <&pwm0 0 25000 0>;
-		};
-	};
-
 	vbus_typec: regulator-vbus-typec {
 		compatible = "regulator-fixed";
 		enable-active-high;
@@ -101,15 +51,6 @@ vbus_typec: regulator-vbus-typec {
 		vin-supply = <&vcc5v0_sys>;
 	};
 
-	vcc5v0_sys: regulator-vcc5v0-sys {
-		compatible = "regulator-fixed";
-		regulator-name = "vcc5v0_sys";
-		regulator-always-on;
-		regulator-boot-on;
-		regulator-min-microvolt = <5000000>;
-		regulator-max-microvolt = <5000000>;
-	};
-
 	vcc_3v3_sd_s0: regulator-vcc-3v3-sd-s0 {
 		compatible = "regulator-fixed";
 		gpios = <&gpio4 RK_PB5 GPIO_ACTIVE_LOW>;
@@ -119,6 +60,15 @@ vcc_3v3_sd_s0: regulator-vcc-3v3-sd-s0 {
 		regulator-max-microvolt = <3300000>;
 		vin-supply = <&vcc_3v3_s3>;
 	};
+
+	vcc5v0_sys: regulator-vcc5v0-sys {
+		compatible = "regulator-fixed";
+		regulator-name = "vcc5v0_sys";
+		regulator-always-on;
+		regulator-boot-on;
+		regulator-min-microvolt = <5000000>;
+		regulator-max-microvolt = <5000000>;
+	};
 };
 
 &combphy0_ps {
@@ -161,20 +111,6 @@ &cpu_l3 {
 	cpu-supply = <&vdd_cpu_lit_s0>;
 };
 
-&gmac1 {
-	clock_in_out = "output";
-	phy-handle = <&rgmii_phy1>;
-	phy-mode = "rgmii-rxid";
-	pinctrl-0 = <&gmac1_miim
-		     &gmac1_tx_bus2
-		     &gmac1_rx_bus2
-		     &gmac1_rgmii_clk
-		     &gmac1_rgmii_bus>;
-	pinctrl-names = "default";
-	tx_delay = <0x42>;
-	status = "okay";
-};
-
 &gpu {
 	mali-supply = <&vdd_gpu_s0>;
 	status = "okay";
@@ -270,69 +206,6 @@ &i2c6 {
 	pinctrl-0 = <&i2c6m3_xfer>;
 	status = "okay";
 
-	es8388: audio-codec@10 {
-		compatible = "everest,es8388", "everest,es8328";
-		reg = <0x10>;
-		clocks = <&cru I2S1_8CH_MCLKOUT>;
-		AVDD-supply = <&vcc_3v3_s0>;
-		DVDD-supply = <&vcc_1v8_s0>;
-		HPVDD-supply = <&vcc_3v3_s0>;
-		PVDD-supply = <&vcc_3v3_s0>;
-		assigned-clocks = <&cru I2S1_8CH_MCLKOUT>;
-		assigned-clock-rates = <12288000>;
-		#sound-dai-cells = <0>;
-	};
-
-	usbc0: usb-typec@22 {
-		compatible = "fcs,fusb302";
-		reg = <0x22>;
-		interrupt-parent = <&gpio0>;
-		interrupts = <RK_PD3 IRQ_TYPE_LEVEL_LOW>;
-		pinctrl-names = "default";
-		pinctrl-0 = <&usbc0_int>;
-		vbus-supply = <&vbus_typec>;
-		status = "okay";
-
-		usb_con: connector {
-			compatible = "usb-c-connector";
-			label = "USB-C";
-			data-role = "dual";
-			op-sink-microwatt = <1000000>;
-			power-role = "dual";
-			sink-pdos =
-				<PDO_FIXED(5000, 1000, PDO_FIXED_USB_COMM)>;
-			source-pdos =
-				<PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)>;
-			try-power-role = "source";
-
-			ports {
-				#address-cells = <1>;
-				#size-cells = <0>;
-
-				port@0 {
-					reg = <0>;
-					usbc0_hs: endpoint {
-						remote-endpoint = <&usb_host0_xhci_drd_sw>;
-					};
-				};
-
-				port@1 {
-					reg = <1>;
-					usbc0_ss: endpoint {
-						remote-endpoint = <&usbdp_phy0_typec_ss>;
-					};
-				};
-
-				port@2 {
-					reg = <2>;
-					usbc0_sbu: endpoint {
-						remote-endpoint = <&usbdp_phy0_typec_sbu>;
-					};
-				};
-			};
-		};
-	};
-
 	hym8563: rtc@51 {
 		compatible = "haoyu,hym8563";
 		reg = <0x51>;
@@ -346,18 +219,6 @@ hym8563: rtc@51 {
 	};
 };
 
-&i2s1_8ch {
-	rockchip,i2s-tx-route = <3 2 1 0>;
-	rockchip,i2s-rx-route = <1 3 2 0>;
-	pinctrl-names = "default";
-	pinctrl-0 = <&i2s1m0_sclk
-	             &i2s1m0_mclk
-	             &i2s1m0_lrck
-	             &i2s1m0_sdi1
-	             &i2s1m0_sdo3>;
-	status = "okay";
-};
-
 &i2s5_8ch {
 	status = "okay";
 };
@@ -404,12 +265,6 @@ typec5v_pwren: typec5v-pwren {
 	};
 };
 
-&pwm0 {
-	pinctrl-0 = <&pwm0m2_pins>;
-	pinctrl-names = "default";
-	status = "okay";
-};
-
 &rknn_core_0 {
 	npu-supply = <&vdd_npu_s0>;
 	sram-supply = <&vdd_npu_s0>;
@@ -841,26 +696,7 @@ &uart2 {
 };
 
 &usbdp_phy0 {
-	mode-switch;
-	orientation-switch;
-	sbu1-dc-gpios = <&gpio4 RK_PA5 GPIO_ACTIVE_HIGH>;
-	sbu2-dc-gpios = <&gpio4 RK_PA7 GPIO_ACTIVE_HIGH>;
 	status = "okay";
-
-	port {
-		#address-cells = <1>;
-		#size-cells = <0>;
-
-		usbdp_phy0_typec_ss: endpoint@0 {
-			reg = <0>;
-			remote-endpoint = <&usbc0_ss>;
-		};
-
-		usbdp_phy0_typec_sbu: endpoint@1 {
-			reg = <1>;
-			remote-endpoint = <&usbc0_sbu>;
-		};
-	};
 };
 
 &usb_host0_ehci {
@@ -872,15 +708,7 @@ &usb_host0_ohci {
 };
 
 &usb_host0_xhci {
-	dr_mode = "otg";
-	usb-role-switch;
 	status = "okay";
-
-	port {
-		usb_host0_xhci_drd_sw: endpoint {
-			remote-endpoint = <&usbc0_hs>;
-		};
-	};
 };
 
 &usb_host1_ehci {
@@ -891,7 +719,7 @@ &usb_host1_ohci {
 	status = "okay";
 };
 
-&usb_host2_xhci {
+&vop {
 	status = "okay";
 };
 
@@ -899,10 +727,6 @@ &vop_mmu {
 	status = "okay";
 };
 
-&vop {
-	status = "okay";
-};
-
 &vp0 {
 	vp0_out_hdmi0: endpoint@ROCKCHIP_VOP2_EP_HDMI0 {
 		reg = <ROCKCHIP_VOP2_EP_HDMI0>;
diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5b.dts b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5b.dts
index d21ec320d295..8af174777809 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5b.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5b.dts
@@ -2,7 +2,7 @@
 
 /dts-v1/;
 
-#include "rk3588s-orangepi-5.dtsi"
+#include "rk3588s-orangepi-5-5b.dtsi"
 
 / {
 	model = "Xunlong Orange Pi 5B";
-- 
2.53.0


^ permalink raw reply related

* [PATCH v6 1/3] dt-bindings: arm: rockchip: Add Orange Pi 5 Pro
From: dennis @ 2026-04-11  2:47 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner
  Cc: FUKAUMI Naoki, Hsun Lai, Jonas Karlman, Chaoyi Chen, John Clark,
	Michael Opdenacker, Quentin Schulz, Andrew Lunn, Chukun Pan,
	Alexey Charkov, Peter Robinson, Dennis Gilmore, Michael Riesch,
	Mykola Kvach, Jimmy Hon, devicetree, linux-arm-kernel,
	linux-rockchip, linux-kernel, Krzysztof Kozlowski
In-Reply-To: <20260411024743.195385-1-dennis@ausil.us>

From: Dennis Gilmore <dennis@ausil.us>

Add compatible string for the Orange Pi 5 Pro.

Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Dennis Gilmore <dennis@ausil.us>
---
 Documentation/devicetree/bindings/arm/rockchip.yaml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/arm/rockchip.yaml b/Documentation/devicetree/bindings/arm/rockchip.yaml
index ae77ded9fe47..3c6b83a84463 100644
--- a/Documentation/devicetree/bindings/arm/rockchip.yaml
+++ b/Documentation/devicetree/bindings/arm/rockchip.yaml
@@ -1320,6 +1320,7 @@ properties:
         items:
           - enum:
               - xunlong,orangepi-5
+              - xunlong,orangepi-5-pro
               - xunlong,orangepi-5b
           - const: rockchip,rk3588s
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH v6 0/3] Add support for Orange Pi 5 Pro
From: dennis @ 2026-04-11  2:47 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner
  Cc: FUKAUMI Naoki, Hsun Lai, Jonas Karlman, Chaoyi Chen, John Clark,
	Michael Opdenacker, Quentin Schulz, Andrew Lunn, Chukun Pan,
	Alexey Charkov, Peter Robinson, Dennis Gilmore, Michael Riesch,
	Mykola Kvach, Jimmy Hon, devicetree, linux-arm-kernel,
	linux-rockchip, linux-kernel

From: Dennis Gilmore <dennis@ausil.us>

This series adds initial support for Orange Pi 5 Pro. The PCIe attached network
driver(dwmac-motorcomm) was just added.


The series was tested against Linux 7.0-rc7

Please take a look.

Thank you,

Dennis Gilmore
Changes in v6:
- Move the shared configs for the Orange Pi 5 and Orange Pi 5b from each
  devices dts to a shared rk3588s-orangepi-5-5b.dtsi to avoid duplication
- Remove empty ports subnodeis from typea_con
- Move i2s2m1_mclk pinctrl from &i2s2 to the es8388 codec node
- Add dp-con, dp0_out, dp0_in, and vp1 nodes, plus the vcc3v3_dp regulator
  in order to get the second HDMI port working via its transparent
  LT8711UXD DP to HDMI bridge
- link to v5 https://lore.kernel.org/linux-devicetree/20260401010707.2584962-1-dennis@ausil.us/

Changes in v5:
- define a connector node for Type-A port, and list the regulator as its VBUS supply explicitly.
- Requires https://lore.kernel.org/all/20260217-typea-vbus-v1-1-657b4e55a4c2@flipper.net/
- link to v4 https://lore.kernel.org/linux-devicetree/20260310031002.3921234-1-dennis@ausil.us/

Changes in v4:
- rename vcc3v3_pcie20 copied from rk3588s-orangepi-5.dts to vcc3v3_phy1 to match the schematic
- use vcc_3v3_s3 as the supply not vcc5v0_sys for PCIe
- remove the definition for vcc3v3_pcie_m2 as it does not really exist
  as a regulator

- link to v3 https://lore.kernel.org/linux-devicetree/20260306024634.239614-1-dennis@ausil.us/

Changes in v3:
- moved leds from gpio-leds to pwm-leds
- remove disable-wp from sdio
- rename vcc3v3_pcie_eth regulator to vcc3v3_pcie_m2 to reflect the
  purppose
- actually clean up the delete lines and comments missed in v2
- link to v2 https://lore.kernel.org/linux-devicetree/20260304025521.210377-1-dennis@ausil.us/

Changes in v2:
- moved items not shared by orangepi 5/5b/5 Pro from dtsi to 5 and 5b
  dts files
- removed all the comments and deleted properties from 5 Pro dts
- Link to v1 https://lore.kernel.org/linux-devicetree/20260228205418.2944620-1-dennis@ausil.us/





Dennis Gilmore (3):
  dt-bindings: arm: rockchip: Add Orange Pi 5 Pro
  arm64: dts: rockchip: refactor items from Orange Pi 5/b to prep for
    Pro
  arm64: dts: rockchip: Add Orange Pi 5 Pro board support

 .../devicetree/bindings/arm/rockchip.yaml     |   1 +
 .../display/rockchip/rockchip,dw-dp.yaml      |   7 +
 arch/arm64/boot/dts/rockchip/Makefile         |   1 +
 .../dts/rockchip/rk3588s-orangepi-5-5b.dtsi   | 192 ++++++++++
 .../dts/rockchip/rk3588s-orangepi-5-pro.dts   | 352 ++++++++++++++++++
 .../boot/dts/rockchip/rk3588s-orangepi-5.dts  |   6 +-
 .../boot/dts/rockchip/rk3588s-orangepi-5.dtsi | 198 +---------
 .../boot/dts/rockchip/rk3588s-orangepi-5b.dts |   2 +-
 drivers/gpu/drm/bridge/synopsys/dw-dp.c       |  12 +
 9 files changed, 582 insertions(+), 189 deletions(-)
 create mode 100644 arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5-5b.dtsi
 create mode 100644 arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5-pro.dts

-- 
2.53.0


^ permalink raw reply

* Re: [PATCH 04/35] irqchip/qcom-pdc: Replace pdc_version global with a function pointer
From: Bjorn Andersson @ 2026-04-11  2:43 UTC (permalink / raw)
  To: Mukesh Ojha
  Cc: Thomas Gleixner, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Konrad Dybcio, cros-qcom-dts-watchers, linux-arm-msm,
	linux-kernel, devicetree
In-Reply-To: <20260410184124.1068210-5-mukesh.ojha@oss.qualcomm.com>

On Sat, Apr 11, 2026 at 12:10:41AM +0530, Mukesh Ojha wrote:
> Now that the two enable paths are separate functions, replace the
> pdc_version global with a __pdc_enable_intr function pointer. The
> pointer is assigned once at probe time based on the version register,
> moving the version comparison out of the interrupt enable/disable hot
> path entirely.

That's what the patch does, but why?

> 
> Signed-off-by: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com>
> ---
>  drivers/irqchip/qcom-pdc.c | 13 +++----------
>  1 file changed, 3 insertions(+), 10 deletions(-)
> 
> diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c
> index 21e2b4b884ee..734576cdce0c 100644
> --- a/drivers/irqchip/qcom-pdc.c
> +++ b/drivers/irqchip/qcom-pdc.c
> @@ -51,7 +51,7 @@ static void __iomem *pdc_base;
>  static void __iomem *pdc_prev_base;
>  static struct pdc_pin_region *pdc_region;
>  static int pdc_region_cnt;
> -static unsigned int pdc_version;
> +static void (*__pdc_enable_intr)(int pin_out, bool on);
>  static bool pdc_x1e_quirk;
>  
>  static void pdc_base_reg_write(void __iomem *base, int reg, u32 i, u32 val)
> @@ -123,14 +123,6 @@ static void pdc_enable_intr_cfg(int pin_out, bool on)
>  	pdc_reg_write(IRQ_i_CFG, pin_out, enable);
>  }
>  
> -static void __pdc_enable_intr(int pin_out, bool on)
> -{
> -	if (pdc_version < PDC_VERSION_3_2)
> -		pdc_enable_intr_bank(pin_out, on);
> -	else
> -		pdc_enable_intr_cfg(pin_out, on);

This style is comfortable to read.

> -}
> -
>  static void pdc_enable_intr(struct irq_data *d, bool on)
>  {
>  	unsigned long flags;
> @@ -400,7 +392,8 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare
>  		goto fail;
>  	}
>  
> -	pdc_version = pdc_reg_read(PDC_VERSION_REG, 0);
> +	__pdc_enable_intr = (pdc_reg_read(PDC_VERSION_REG, 0) < PDC_VERSION_3_2) ?
> +			pdc_enable_intr_bank : pdc_enable_intr_cfg;

This style is a mess.

Regards,
Bjorn

>  
>  	parent_domain = irq_find_host(parent);
>  	if (!parent_domain) {
> -- 
> 2.53.0
> 

^ permalink raw reply

* Re: [PATCH v4 2/3] thermal: spacemit: k1: Add thermal sensor support
From: Shuwei Wu @ 2026-04-11  1:56 UTC (permalink / raw)
  To: Jie Gan, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
	Lukasz Luba, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Yixun Lan, Philipp Zabel, Paul Walmsley, Palmer Dabbelt,
	Albert Ou, Alexandre Ghiti
  Cc: linux-pm, devicetree, linux-riscv, spacemit, linux-kernel,
	Anand Moon, Troy Mitchell, Yao Zi, Vincent Legoll, Gong Shuai
In-Reply-To: <38088fd6-1db6-4591-b489-33b52f727549@oss.qualcomm.com>

On Fri Apr 10, 2026 at 4:25 PM CST, Jie Gan wrote:
>
>
> On 4/10/2026 11:31 AM, Shuwei Wu wrote:
>> The thermal sensor on K1 supports monitoring five temperature zones.
>> The driver registers these sensors with the thermal framework
>> and supports standard operations:
>> - Reading temperature (millidegree Celsius)
>> - Setting high/low thresholds for interrupts
>> 
>> Signed-off-by: Shuwei Wu <shuwei.wu@mailbox.org>
>> Reviewed-by: Anand Moon <linux.amoon@gmail.com>
>> Tested-by: Anand Moon <linux.amoon@gmail.com>
>> Reviewed-by: Troy Mitchell <troy.mitchell@linux.spacemit.com>
>> Reviewed-by: Yao Zi <me@ziyao.cc>
>> Tested-by: Vincent Legoll <legoll@online.fr> # OrangePi-RV2
>> Tested-by: Gong Shuai <gsh517025@gmail.com>
>> 
>> ---
>> Changes in v4:
>> - Add 'depends on THERMAL_OF' in drivers/thermal/spacemit/Kconfig
>> 
>> Changes in v3:
>> - Align multi-line assignments as suggested by reviewer
>> - Remove unnecessary variable definitions
>> 
>> Changes in v2:
>> - Rename k1_thermal.c to k1_tsensor.c for better hardware alignment
>> - Move driver to drivers/thermal/spacemit/
>> - Add Kconfig/Makefile for spacemit and update top-level build files
>> - Refactor names, style, code alignment, and comments
>> - Simplify probe and error handling
>> ---
>>   drivers/thermal/Kconfig               |   2 +
>>   drivers/thermal/Makefile              |   1 +
>>   drivers/thermal/spacemit/Kconfig      |  19 +++
>>   drivers/thermal/spacemit/Makefile     |   3 +
>>   drivers/thermal/spacemit/k1_tsensor.c | 281 ++++++++++++++++++++++++++++++++++
>>   5 files changed, 306 insertions(+)
>> 
>> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
>> index b10080d61860..1c4a5cd5a23e 100644
>> --- a/drivers/thermal/Kconfig
>> +++ b/drivers/thermal/Kconfig
>> @@ -472,6 +472,8 @@ endmenu
>>   
>>   source "drivers/thermal/renesas/Kconfig"
>>   
>> +source "drivers/thermal/spacemit/Kconfig"
>> +
>>   source "drivers/thermal/tegra/Kconfig"
>>   
>>   config GENERIC_ADC_THERMAL
>> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
>> index bb21e7ea7fc6..3b249195c088 100644
>> --- a/drivers/thermal/Makefile
>> +++ b/drivers/thermal/Makefile
>> @@ -65,6 +65,7 @@ obj-y				+= mediatek/
>>   obj-$(CONFIG_GENERIC_ADC_THERMAL)	+= thermal-generic-adc.o
>>   obj-$(CONFIG_UNIPHIER_THERMAL)	+= uniphier_thermal.o
>>   obj-$(CONFIG_AMLOGIC_THERMAL)     += amlogic_thermal.o
>> +obj-y				+= spacemit/
>>   obj-$(CONFIG_SPRD_THERMAL)	+= sprd_thermal.o
>>   obj-$(CONFIG_KHADAS_MCU_FAN_THERMAL)	+= khadas_mcu_fan.o
>>   obj-$(CONFIG_LOONGSON2_THERMAL)	+= loongson2_thermal.o
>> diff --git a/drivers/thermal/spacemit/Kconfig b/drivers/thermal/spacemit/Kconfig
>> new file mode 100644
>> index 000000000000..de7b5ece5af2
>> --- /dev/null
>> +++ b/drivers/thermal/spacemit/Kconfig
>> @@ -0,0 +1,19 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +menu "SpacemiT thermal drivers"
>> +depends on ARCH_SPACEMIT || COMPILE_TEST
>> +
>> +config SPACEMIT_K1_TSENSOR
>> +	tristate "SpacemiT K1 thermal sensor driver"
>> +	depends on THERMAL_OF
>> +	help
>> +	  This driver provides support for the thermal sensor
>> +	  integrated in the SpacemiT K1 SoC.
>> +
>> +	  The thermal sensor monitors temperatures for five thermal zones:
>> +	  soc, package, gpu, cluster0, and cluster1. It supports reporting
>> +	  temperature values and handling high/low threshold interrupts.
>> +
>> +	  Say Y here if you want to enable thermal monitoring on SpacemiT K1.
>> +	  If compiled as a module, it will be called k1_tsensor.
>> +
>> +endmenu
>> diff --git a/drivers/thermal/spacemit/Makefile b/drivers/thermal/spacemit/Makefile
>> new file mode 100644
>> index 000000000000..82b30741e4ec
>> --- /dev/null
>> +++ b/drivers/thermal/spacemit/Makefile
>> @@ -0,0 +1,3 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +
>> +obj-$(CONFIG_SPACEMIT_K1_TSENSOR)	+= k1_tsensor.o
>> diff --git a/drivers/thermal/spacemit/k1_tsensor.c b/drivers/thermal/spacemit/k1_tsensor.c
>> new file mode 100644
>> index 000000000000..b742739e9019
>> --- /dev/null
>> +++ b/drivers/thermal/spacemit/k1_tsensor.c
>> @@ -0,0 +1,281 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Thermal sensor driver for SpacemiT K1 SoC
>> + *
>> + * Copyright (C) 2026 Shuwei Wu <shuwei.wu@mailbox.org>
>> + */
>> +#include <linux/bitfield.h>
>> +#include <linux/clk.h>
>> +#include <linux/err.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/io.h>
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/reset.h>
>> +#include <linux/slab.h>
>> +#include <linux/thermal.h>
>> +
>> +#include "../thermal_hwmon.h"
>> +
>> +#define K1_TSENSOR_PCTRL_REG		0x00
>> +#define K1_TSENSOR_PCTRL_ENABLE		BIT(0)
>> +#define K1_TSENSOR_PCTRL_TEMP_MODE	BIT(3)
>> +#define K1_TSENSOR_PCTRL_RAW_SEL	BIT(7)
>> +
>> +#define K1_TSENSOR_PCTRL_CTUNE		GENMASK(11, 8)
>> +#define K1_TSENSOR_PCTRL_SW_CTRL	GENMASK(21, 18)
>> +#define K1_TSENSOR_PCTRL_HW_AUTO_MODE	BIT(23)
>> +
>> +#define K1_TSENSOR_EN_REG		0x08
>> +#define K1_TSENSOR_EN_ALL		GENMASK(MAX_SENSOR_NUMBER - 1, 0)
>> +
>> +#define K1_TSENSOR_TIME_REG		0x0C
>> +#define K1_TSENSOR_TIME_WAIT_REF_CNT	GENMASK(3, 0)
>> +#define K1_TSENSOR_TIME_ADC_CNT_RST	GENMASK(7, 4)
>> +#define K1_TSENSOR_TIME_FILTER_PERIOD	GENMASK(21, 20)
>> +#define K1_TSENSOR_TIME_MASK		GENMASK(23, 0)
>> +
>> +#define K1_TSENSOR_INT_CLR_REG		0x10
>> +#define K1_TSENSOR_INT_EN_REG		0x14
>> +#define K1_TSENSOR_INT_STA_REG		0x18
>> +
>> +#define K1_TSENSOR_INT_EN_MASK		BIT(0)
>> +#define K1_TSENSOR_INT_MASK(x)		(GENMASK(2, 1) << ((x) * 2))
>> +
>> +#define K1_TSENSOR_DATA_BASE_REG	0x20
>> +#define K1_TSENSOR_DATA_REG(x)		(K1_TSENSOR_DATA_BASE_REG + ((x) / 2) * 4)
>> +#define K1_TSENSOR_DATA_LOW_MASK	GENMASK(15, 0)
>> +#define K1_TSENSOR_DATA_HIGH_MASK	GENMASK(31, 16)
>> +
>> +#define K1_TSENSOR_THRSH_BASE_REG	0x40
>> +#define K1_TSENSOR_THRSH_REG(x)		(K1_TSENSOR_THRSH_BASE_REG + ((x) * 4))
>> +#define K1_TSENSOR_THRSH_LOW_MASK	GENMASK(15, 0)
>> +#define K1_TSENSOR_THRSH_HIGH_MASK	GENMASK(31, 16)
>> +
>> +#define MAX_SENSOR_NUMBER		5
>> +
>> +/* Hardware offset value required for temperature calculation */
>> +#define TEMPERATURE_OFFSET		278
>> +
>> +struct k1_tsensor_channel {
>> +	struct k1_tsensor *ts;
>> +	struct thermal_zone_device *tzd;
>> +	int id;
>> +};
>> +
>> +struct k1_tsensor {
>> +	void __iomem *base;
>> +	struct k1_tsensor_channel ch[MAX_SENSOR_NUMBER];
>> +};
>> +
>> +static void k1_tsensor_init(struct k1_tsensor *ts)
>> +{
>> +	u32 val;
>> +
>> +	/* Disable all the interrupts */
>> +	writel(0xffffffff, ts->base + K1_TSENSOR_INT_EN_REG);
>> +
>> +	/* Configure ADC sampling time and filter period */
>> +	val = readl(ts->base + K1_TSENSOR_TIME_REG);
>> +	val &= ~K1_TSENSOR_TIME_MASK;
>> +	val |= K1_TSENSOR_TIME_FILTER_PERIOD |
>> +	       K1_TSENSOR_TIME_ADC_CNT_RST |
>> +	       K1_TSENSOR_TIME_WAIT_REF_CNT;
>> +	writel(val, ts->base + K1_TSENSOR_TIME_REG);
>> +
>> +	/*
>> +	 * Enable all sensors' auto mode, enable dither control,
>> +	 * consecutive mode, and power up sensor.
>> +	 */
>> +	val = readl(ts->base + K1_TSENSOR_PCTRL_REG);
>> +	val &= ~K1_TSENSOR_PCTRL_SW_CTRL;
>> +	val &= ~K1_TSENSOR_PCTRL_CTUNE;
>> +	val |= K1_TSENSOR_PCTRL_RAW_SEL |
>> +	       K1_TSENSOR_PCTRL_TEMP_MODE |
>> +	       K1_TSENSOR_PCTRL_HW_AUTO_MODE |
>> +	       K1_TSENSOR_PCTRL_ENABLE;
>> +	writel(val, ts->base + K1_TSENSOR_PCTRL_REG);
>> +
>> +	/* Enable thermal interrupt */
>> +	val = readl(ts->base + K1_TSENSOR_INT_EN_REG);
>> +	val |= K1_TSENSOR_INT_EN_MASK;
>> +	writel(val, ts->base + K1_TSENSOR_INT_EN_REG);
>> +
>> +	/* Enable each sensor */
>> +	val = readl(ts->base + K1_TSENSOR_EN_REG);
>> +	val |= K1_TSENSOR_EN_ALL;
>> +	writel(val, ts->base + K1_TSENSOR_EN_REG);
>> +}
>> +
>> +static void k1_tsensor_enable_irq(struct k1_tsensor_channel *ch)
>> +{
>> +	struct k1_tsensor *ts = ch->ts;
>> +	u32 val;
>> +
>> +	val = readl(ts->base + K1_TSENSOR_INT_CLR_REG);
>> +	val |= K1_TSENSOR_INT_MASK(ch->id);
>> +	writel(val, ts->base + K1_TSENSOR_INT_CLR_REG);
>> +
>> +	val = readl(ts->base + K1_TSENSOR_INT_EN_REG);
>> +	val &= ~K1_TSENSOR_INT_MASK(ch->id);
>> +	writel(val, ts->base + K1_TSENSOR_INT_EN_REG);
>> +}
>> +
>> +/*
>> + * The conversion formula used is:
>> + * T(m°C) = (((raw_value & mask) >> shift) - TEMPERATURE_OFFSET) * 1000
>> + */
>> +static int k1_tsensor_get_temp(struct thermal_zone_device *tz, int *temp)
>> +{
>> +	struct k1_tsensor_channel *ch = thermal_zone_device_priv(tz);
>> +	struct k1_tsensor *ts = ch->ts;
>> +	u32 val;
>> +
>> +	val = readl(ts->base + K1_TSENSOR_DATA_REG(ch->id));
>> +	if (ch->id % 2)
>> +		*temp = FIELD_GET(K1_TSENSOR_DATA_HIGH_MASK, val);
>> +	else
>> +		*temp = FIELD_GET(K1_TSENSOR_DATA_LOW_MASK, val);
>> +
>> +	*temp -= TEMPERATURE_OFFSET;
>> +	*temp *= 1000;
>> +
>> +	return 0;
>> +}
>> +
>> +/*
>> + * For each sensor, the hardware threshold register is 32 bits:
>> + * - Lower 16 bits [15:0] configure the low threshold temperature.
>> + * - Upper 16 bits [31:16] configure the high threshold temperature.
>> + */
>> +static int k1_tsensor_set_trips(struct thermal_zone_device *tz, int low, int high)
>> +{
>> +	struct k1_tsensor_channel *ch = thermal_zone_device_priv(tz);
>> +	struct k1_tsensor *ts = ch->ts;
>> +	u32 val;
>> +
>> +	if (low >= high)
>> +		return -EINVAL;
>> +
>> +	if (low < 0)
>> +		low = 0;
>> +
>> +	high = high / 1000 + TEMPERATURE_OFFSET;
>
> Consider passes high = INT_MAX here:
>
> high = INT/1000 + TEMPERATURE_OFFSET == 2147761;
>
>> +	low = low / 1000 + TEMPERATURE_OFFSET;
>> +
>> +	val = readl(ts->base + K1_TSENSOR_THRSH_REG(ch->id));
>> +	val &= ~K1_TSENSOR_THRSH_HIGH_MASK;
>> +	val |= FIELD_PREP(K1_TSENSOR_THRSH_HIGH_MASK, high);
>
> K1_TSENSOR_THRSH_HIGH_MASK is a 16-bit MASK:
> FIELD_PREP(K1_TSENSOR_THRSH_HIGH_MASK, 2147761); <- overflow happened
>
> the maximum value here will be changed to 50609 from 65536.
>
> We should add a check here and limit the 'high' value here to avoid 
> overflow:
>
> if (high > (int)((0xFFFF - TEMPERATURE_OFFSET) * 1000))
> 	high = (0Xffff - TEMPERATURE_OFFSET) * 1000;
>
> high = high / 1000 + TEMPERATURE_OFFSET;
>
> ...

Got it. I'll add a check to handle the overflow.

> 	
>> +
>> +	val &= ~K1_TSENSOR_THRSH_LOW_MASK;
>> +	val |= FIELD_PREP(K1_TSENSOR_THRSH_LOW_MASK, low);
>> +	writel(val, ts->base + K1_TSENSOR_THRSH_REG(ch->id));
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct thermal_zone_device_ops k1_tsensor_ops = {
>> +	.get_temp = k1_tsensor_get_temp,
>> +	.set_trips = k1_tsensor_set_trips,
>> +};
>> +
>> +static irqreturn_t k1_tsensor_irq_thread(int irq, void *data)
>> +{
>> +	struct k1_tsensor *ts = (struct k1_tsensor *)data;
>> +	int mask, status, i;
>> +
>> +	status = readl(ts->base + K1_TSENSOR_INT_STA_REG);
>> +
>> +	for (i = 0; i < MAX_SENSOR_NUMBER; i++) {
>> +		if (status & K1_TSENSOR_INT_MASK(i)) {
>> +			mask = readl(ts->base + K1_TSENSOR_INT_CLR_REG);
>> +			mask |= K1_TSENSOR_INT_MASK(i);
>> +			writel(mask, ts->base + K1_TSENSOR_INT_CLR_REG);
>> +			thermal_zone_device_update(ts->ch[i].tzd, THERMAL_EVENT_UNSPECIFIED);
>> +		}
>> +	}
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static int k1_tsensor_probe(struct platform_device *pdev)
>> +{
>> +	struct device *dev = &pdev->dev;
>> +	struct k1_tsensor *ts;
>> +	struct reset_control *reset;
>> +	struct clk *clk;
>> +	int i, irq, ret;
>> +
>> +	ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
>> +	if (!ts)
>> +		return -ENOMEM;
>> +
>> +	ts->base = devm_platform_ioremap_resource(pdev, 0);
>> +	if (IS_ERR(ts->base))
>> +		return dev_err_probe(dev, PTR_ERR(ts->base), "Failed to get reg\n");
>> +
>> +	reset = devm_reset_control_get_exclusive_deasserted(dev, NULL);
>> +	if (IS_ERR(reset))
>> +		return dev_err_probe(dev, PTR_ERR(reset), "Failed to get/deassert reset control\n");
>> +
>> +	clk = devm_clk_get_enabled(dev, "core");
>> +	if (IS_ERR(clk))
>> +		return dev_err_probe(dev, PTR_ERR(clk), "Failed to get core clock\n");
>> +
>> +	clk = devm_clk_get_enabled(dev, "bus");
>> +	if (IS_ERR(clk))
>> +		return dev_err_probe(dev, PTR_ERR(clk), "Failed to get bus clock\n");
>> +
>> +	k1_tsensor_init(ts);
>> +
>> +	for (i = 0; i < MAX_SENSOR_NUMBER; ++i) {
>> +		ts->ch[i].id = i;
>> +		ts->ch[i].ts = ts;
>> +		ts->ch[i].tzd = devm_thermal_of_zone_register(dev, i, ts->ch + i, &k1_tsensor_ops);
>> +		if (IS_ERR(ts->ch[i].tzd))
>> +			return PTR_ERR(ts->ch[i].tzd);
>> +
>> +		/* Attach sysfs hwmon attributes for userspace monitoring */
>> +		ret = devm_thermal_add_hwmon_sysfs(dev, ts->ch[i].tzd);
>> +		if (ret)
>> +			dev_warn(dev, "Failed to add hwmon sysfs attributes\n");
>> +
>> +		k1_tsensor_enable_irq(ts->ch + i);
>
> should call after the devm_request_threaded_irq succeeds;

I'll reorder this in v5.
Thanks for catching that.

>
> Thanks,
> Jie
>
>> +	}
>> +
>> +	irq = platform_get_irq(pdev, 0);
>> +	if (irq < 0)
>> +		return irq;
>> +
>> +	ret = devm_request_threaded_irq(dev, irq, NULL,
>> +					k1_tsensor_irq_thread,
>> +					IRQF_ONESHOT, "k1_tsensor", ts);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	platform_set_drvdata(pdev, ts);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id k1_tsensor_dt_ids[] = {
>> +	{ .compatible = "spacemit,k1-tsensor" },
>> +	{ /* sentinel */ }
>> +};
>> +
>> +MODULE_DEVICE_TABLE(of, k1_tsensor_dt_ids);
>> +
>> +static struct platform_driver k1_tsensor_driver = {
>> +	.driver = {
>> +		.name		= "k1_tsensor",
>> +		.of_match_table = k1_tsensor_dt_ids,
>> +	},
>> +	.probe	= k1_tsensor_probe,
>> +};
>> +module_platform_driver(k1_tsensor_driver);
>> +
>> +MODULE_DESCRIPTION("SpacemiT K1 Thermal Sensor Driver");
>> +MODULE_AUTHOR("Shuwei Wu <shuwei.wu@mailbox.org>");
>> +MODULE_LICENSE("GPL");
>> 

-- 
Best regards,
Shuwei Wu

^ permalink raw reply

* Re: [PATCH] riscv: dts: tenstorrent: Add PMU node to blackhole for Linux perf support
From: Drew Fustini @ 2026-04-11  1:38 UTC (permalink / raw)
  To: Anirudh Srinivasan
  Cc: Drew Fustini, Joel Stanley, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Paul Walmsley, Palmer Dabbelt, Albert Ou,
	Alexandre Ghiti, linux-riscv, devicetree, linux-kernel,
	Michael Neuling
In-Reply-To: <20260409-blackhole_pmu-v1-1-01a34bf46a1c@oss.tenstorrent.com>

On Thu, Apr 09, 2026 at 09:49:59PM -0500, Anirudh Srinivasan wrote:
> From: Michael Neuling <mikey@neuling.org>
> 
> Add a riscv,pmu device tree node with SBI PMU event mappings for the
> SiFive X280 hardware performance counters. This enables OpenSBI to
> expose the SBI PMU extension, allowing Linux perf to use the 4
> programmable counters (mhpmcounter3-6) across 3 event classes:
> instruction commit, microarchitectural, and memory system events.
> 
> Event encodings are derived from the SiFive Tenstorrent X280 MC Manual
> (21G3.04.00) Table 13, section 3.10.5.
> 
> Assisted-by: Claude:claude-opus-4-6[1m]
> Signed-off-by: Michael Neuling <mikey@neuling.org>
> Signed-off-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
> ---
> Added a dependency of [1] to b4 so that checkpatch doesn't complain
> about the Assisted-by tag
> 
> [1] https://lore.kernel.org/all/20260311152039.254244-1-sashal@kernel.org/
> ---
>  arch/riscv/boot/dts/tenstorrent/blackhole.dtsi | 48 ++++++++++++++++++++++++++
>  1 file changed, 48 insertions(+)

Reviewed-by: Drew Fustini <fustini@kernel.org>

If there are no objections, then I will apply it to
tenstorrent-dt-for-next.

Thanks,
Drew

^ permalink raw reply

* Re: [PATCH v4 1/3] dt-bindings: thermal: Add SpacemiT K1 thermal sensor
From: Shuwei Wu @ 2026-04-11  1:36 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Yixun Lan,
	Philipp Zabel, Paul Walmsley, Palmer Dabbelt, Albert Ou,
	Alexandre Ghiti, linux-pm, devicetree, linux-riscv, spacemit,
	linux-kernel, Krzysztof Kozlowski, Vincent Legoll, Gong Shuai
In-Reply-To: <20260410-inescapable-glossy-cobra-da4bf4@quoll>

On Fri Apr 10, 2026 at 3:22 PM CST, Krzysztof Kozlowski wrote:
> On Fri, Apr 10, 2026 at 11:31:36AM +0800, Shuwei Wu wrote:
>> Document the SpacemiT K1 Thermal Sensor, which supports
>> monitoring temperatures for five zones: soc, package, gpu, cluster0,
>> and cluster1.
>> 
>> Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
>> Signed-off-by: Shuwei Wu <shuwei.wu@mailbox.org>
>> Tested-by: Vincent Legoll <legoll@online.fr> # OrangePi-RV2
>> Tested-by: Gong Shuai <gsh517025@gmail.com>
>
> No, not possible. Otherwise explain me how your device tested a YAML
> file.
>
> Drop all of such tags.
Sorry for the noise. I will drop it.
Thanks for pointing out.
>
> Best regards,
> Krzysztof

-- 
Best regards,
Shuwei Wu

^ permalink raw reply

* [PATCH v2 3/3] pmdomain: arm_scmi: add support for domain hierarchies
From: Kevin Hilman (TI) @ 2026-04-10 23:44 UTC (permalink / raw)
  To: Ulf Hansson, Rob Herring
  Cc: Geert Uytterhoeven, linux-pm, devicetree, linux-kernel, arm-scmi,
	linux-arm-kernel
In-Reply-To: <20260410-topic-lpm-pmdomain-child-ids-v2-0-83396e4b5f8b@baylibre.com>

After primary SCMI pmdomain is created, use new of_genpd helper which
checks for child domain mappings defined in power-domains-child-ids.

Also remove any child domain mappings when SCMI domain is removed.

Signed-off-by: Kevin Hilman (TI) <khilman@baylibre.com>
---
 drivers/pmdomain/arm/scmi_pm_domain.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/drivers/pmdomain/arm/scmi_pm_domain.c b/drivers/pmdomain/arm/scmi_pm_domain.c
index b5e2ffd5ea64..6d33b3d62ef3 100644
--- a/drivers/pmdomain/arm/scmi_pm_domain.c
+++ b/drivers/pmdomain/arm/scmi_pm_domain.c
@@ -114,6 +114,14 @@ static int scmi_pm_domain_probe(struct scmi_device *sdev)
 
 	dev_set_drvdata(dev, scmi_pd_data);
 
+	/*
+	 * Parse (optional) power-domains-child-ids property to
+	 * establish parent-child relationships
+	 */
+	ret = of_genpd_add_child_ids(np, scmi_pd_data);
+	if (ret < 0 && ret != -ENOENT)
+		dev_err(dev, "Failed to add child domain hierarchy: %d\n", ret);
+
 	return 0;
 err_rm_genpds:
 	for (i = num_domains - 1; i >= 0; i--)
@@ -129,9 +137,13 @@ static void scmi_pm_domain_remove(struct scmi_device *sdev)
 	struct device *dev = &sdev->dev;
 	struct device_node *np = dev->of_node;
 
+	scmi_pd_data = dev_get_drvdata(dev);
+
+	/* Remove any parent-child relationships established at probe time */
+	of_genpd_remove_child_ids(np, scmi_pd_data);
+
 	of_genpd_del_provider(np);
 
-	scmi_pd_data = dev_get_drvdata(dev);
 	for (i = 0; i < scmi_pd_data->num_domains; i++) {
 		if (!scmi_pd_data->domains[i])
 			continue;

-- 
2.51.0


^ permalink raw reply related

* [PATCH v2 2/3] pmdomain: core: add support for power-domains-child-ids
From: Kevin Hilman (TI) @ 2026-04-10 23:44 UTC (permalink / raw)
  To: Ulf Hansson, Rob Herring
  Cc: Geert Uytterhoeven, linux-pm, devicetree, linux-kernel, arm-scmi,
	linux-arm-kernel
In-Reply-To: <20260410-topic-lpm-pmdomain-child-ids-v2-0-83396e4b5f8b@baylibre.com>

Currently, PM domains can only support hierarchy for simple
providers (e.g. ones with #power-domain-cells = 0).

Add support for oncell providers as well by adding a new property
`power-domains-child-ids` to describe the parent/child relationship.

For example, an SCMI PM domain provider has multiple domains, each of
which might be a child of diffeent parent domains. In this example,
the parent domains are MAIN_PD and WKUP_PD:

    scmi_pds: protocol@11 {
        reg = <0x11>;
        #power-domain-cells = <1>;
        power-domains = <&MAIN_PD>, <&WKUP_PD>;
        power-domains-child-ids = <15>, <19>;
    };

With this example using the new property, SCMI PM domain 15 becomes a
child domain of MAIN_PD, and SCMI domain 19 becomes a child domain of
WKUP_PD.

To support this feature, add two new core functions

- of_genpd_add_child_ids()
- of_genpd_remove_child_ids()

which can be called by pmdomain providers to add/remove child domains
if they support the new property power-domains-child-ids.

The add function is "all or nothing".  If it cannot add all of the
child domains in the list, it will unwind any additions already made
and report a failure.

Signed-off-by: Kevin Hilman (TI) <khilman@baylibre.com>
---
 drivers/pmdomain/core.c   | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/pm_domain.h |  16 ++++++++++++++++
 2 files changed, 182 insertions(+)

diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c
index 61c2277c9ce3..f978477dd546 100644
--- a/drivers/pmdomain/core.c
+++ b/drivers/pmdomain/core.c
@@ -2909,6 +2909,172 @@ static struct generic_pm_domain *genpd_get_from_provider(
 	return genpd;
 }
 
+/**
+ * of_genpd_add_child_ids() - Parse power-domains-child-ids property
+ * @np: Device node pointer associated with the PM domain provider.
+ * @data: Pointer to the onecell data associated with the PM domain provider.
+ *
+ * Parse the power-domains and power-domains-child-ids properties to establish
+ * parent-child relationships for PM domains. The power-domains property lists
+ * parent domains, and power-domains-child-ids lists which child domain IDs
+ * should be associated with each parent.
+ *
+ * Uses "all or nothing" semantics: either all relationships are established
+ * successfully, or none are (any partially-added relationships are unwound
+ * on error).
+ *
+ * Returns 0 on success, -ENOENT if properties don't exist, or negative error code.
+ */
+int of_genpd_add_child_ids(struct device_node *np,
+			   struct genpd_onecell_data *data)
+{
+	struct of_phandle_args parent_args;
+	struct generic_pm_domain *parent_genpd, *child_genpd;
+	struct generic_pm_domain **pairs; /* pairs[2*i]=parent, pairs[2*i+1]=child */
+	u32 child_id;
+	int i, ret, count, child_count, added = 0;
+
+	/* Check if both properties exist */
+	count = of_count_phandle_with_args(np, "power-domains", "#power-domain-cells");
+	if (count <= 0)
+		return -ENOENT;
+
+	child_count = of_property_count_u32_elems(np, "power-domains-child-ids");
+	if (child_count < 0)
+		return -ENOENT;
+	if (child_count != count)
+		return -EINVAL;
+
+	/* Allocate tracking array for error unwind (parent/child pairs) */
+	pairs = kmalloc_array(count * 2, sizeof(*pairs), GFP_KERNEL);
+	if (!pairs)
+		return -ENOMEM;
+
+	for (i = 0; i < count; i++) {
+		ret = of_property_read_u32_index(np, "power-domains-child-ids",
+						 i, &child_id);
+		if (ret)
+			goto err_unwind;
+
+		/* Validate child ID is within bounds */
+		if (child_id >= data->num_domains) {
+			pr_err("Child ID %u out of bounds (max %u) for %pOF\n",
+			       child_id, data->num_domains - 1, np);
+			ret = -EINVAL;
+			goto err_unwind;
+		}
+
+		/* Get the child domain */
+		child_genpd = data->domains[child_id];
+		if (!child_genpd) {
+			pr_err("Child domain %u is NULL for %pOF\n", child_id, np);
+			ret = -EINVAL;
+			goto err_unwind;
+		}
+
+		ret = of_parse_phandle_with_args(np, "power-domains",
+						 "#power-domain-cells", i,
+						 &parent_args);
+		if (ret)
+			goto err_unwind;
+
+		/* Get the parent domain */
+		parent_genpd = genpd_get_from_provider(&parent_args);
+		of_node_put(parent_args.np);
+		if (IS_ERR(parent_genpd)) {
+			pr_err("Failed to get parent domain for %pOF: %ld\n",
+			       np, PTR_ERR(parent_genpd));
+			ret = PTR_ERR(parent_genpd);
+			goto err_unwind;
+		}
+
+		/* Establish parent-child relationship */
+		ret = pm_genpd_add_subdomain(parent_genpd, child_genpd);
+		if (ret) {
+			pr_err("Failed to add child domain %u to parent in %pOF: %d\n",
+			       child_id, np, ret);
+			goto err_unwind;
+		}
+
+		/* Track for potential unwind */
+		pairs[2 * added] = parent_genpd;
+		pairs[2 * added + 1] = child_genpd;
+		added++;
+
+		pr_debug("Added child domain %u (%s) to parent %s for %pOF\n",
+			 child_id, child_genpd->name, parent_genpd->name, np);
+	}
+
+	kfree(pairs);
+	return 0;
+
+err_unwind:
+	/* Reverse all previously established relationships */
+	while (added-- > 0)
+		pm_genpd_remove_subdomain(pairs[2 * added], pairs[2 * added + 1]);
+	kfree(pairs);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(of_genpd_add_child_ids);
+
+/**
+ * of_genpd_remove_child_ids() - Remove parent-child PM domain relationships
+ * @np: Device node pointer associated with the PM domain provider.
+ * @data: Pointer to the onecell data associated with the PM domain provider.
+ *
+ * Reverses the effect of of_genpd_add_child_ids() by parsing the same
+ * power-domains and power-domains-child-ids properties and calling
+ * pm_genpd_remove_subdomain() for each established relationship.
+ *
+ * Returns 0 on success, -ENOENT if properties don't exist, or negative error
+ * code on failure.
+ */
+int of_genpd_remove_child_ids(struct device_node *np,
+			   struct genpd_onecell_data *data)
+{
+	struct of_phandle_args parent_args;
+	struct generic_pm_domain *parent_genpd, *child_genpd;
+	u32 child_id;
+	int i, ret, count, child_count;
+
+	/* Check if both properties exist */
+	count = of_count_phandle_with_args(np, "power-domains", "#power-domain-cells");
+	if (count <= 0)
+		return -ENOENT;
+
+	child_count = of_property_count_u32_elems(np, "power-domains-child-ids");
+	if (child_count < 0)
+		return -ENOENT;
+	if (child_count != count)
+		return -EINVAL;
+
+	for (i = 0; i < count; i++) {
+		if (of_property_read_u32_index(np, "power-domains-child-ids",
+					       i, &child_id))
+			continue;
+
+		if (child_id >= data->num_domains || !data->domains[child_id])
+			continue;
+
+		ret = of_parse_phandle_with_args(np, "power-domains",
+						 "#power-domain-cells", i,
+						 &parent_args);
+		if (ret)
+			continue;
+
+		parent_genpd = genpd_get_from_provider(&parent_args);
+		of_node_put(parent_args.np);
+		if (IS_ERR(parent_genpd))
+			continue;
+
+		child_genpd = data->domains[child_id];
+		pm_genpd_remove_subdomain(parent_genpd, child_genpd);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(of_genpd_remove_child_ids);
+
 /**
  * of_genpd_add_device() - Add a device to an I/O PM domain
  * @genpdspec: OF phandle args to use for look-up PM domain
diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h
index f67a2cb7d781..b44615d79af6 100644
--- a/include/linux/pm_domain.h
+++ b/include/linux/pm_domain.h
@@ -465,6 +465,10 @@ struct generic_pm_domain *of_genpd_remove_last(struct device_node *np);
 int of_genpd_parse_idle_states(struct device_node *dn,
 			       struct genpd_power_state **states, int *n);
 void of_genpd_sync_state(struct device_node *np);
+int of_genpd_add_child_ids(struct device_node *np,
+			   struct genpd_onecell_data *data);
+int of_genpd_remove_child_ids(struct device_node *np,
+			      struct genpd_onecell_data *data);
 
 int genpd_dev_pm_attach(struct device *dev);
 struct device *genpd_dev_pm_attach_by_id(struct device *dev,
@@ -534,6 +538,18 @@ struct generic_pm_domain *of_genpd_remove_last(struct device_node *np)
 {
 	return ERR_PTR(-EOPNOTSUPP);
 }
+
+static inline int of_genpd_add_child_ids(struct device_node *np,
+					 struct genpd_onecell_data *data)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline int of_genpd_remove_child_ids(struct device_node *np,
+					    struct genpd_onecell_data *data)
+{
+	return -EOPNOTSUPP;
+}
 #endif /* CONFIG_PM_GENERIC_DOMAINS_OF */
 
 #ifdef CONFIG_PM

-- 
2.51.0


^ permalink raw reply related

* [PATCH v2 1/3] dt-bindings: power: Add power-domains-child-ids property
From: Kevin Hilman (TI) @ 2026-04-10 23:44 UTC (permalink / raw)
  To: Ulf Hansson, Rob Herring
  Cc: Geert Uytterhoeven, linux-pm, devicetree, linux-kernel, arm-scmi,
	linux-arm-kernel
In-Reply-To: <20260410-topic-lpm-pmdomain-child-ids-v2-0-83396e4b5f8b@baylibre.com>

Add binding documentation for the new power-domains-child-ids property,
which works in conjunction with the existing power-domains property to
establish parent-child relationships between a multi-domain power domain
provider and external parent domains.

Each element in the uint32 array identifies the child domain
ID (index) within the provider that should be made a child domain of
the corresponding phandle entry in power-domains. The two arrays must
have the same number of elements.

Signed-off-by: Kevin Hilman (TI) <khilman@baylibre.com>
---
 Documentation/devicetree/bindings/power/power-domain.yaml | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/Documentation/devicetree/bindings/power/power-domain.yaml b/Documentation/devicetree/bindings/power/power-domain.yaml
index b1147dbf2e73..163b0af158fd 100644
--- a/Documentation/devicetree/bindings/power/power-domain.yaml
+++ b/Documentation/devicetree/bindings/power/power-domain.yaml
@@ -68,6 +68,21 @@ properties:
       by the given provider should be subdomains of the domain specified
       by this binding.
 
+  power-domains-child-ids:
+    $ref: /schemas/types.yaml#/definitions/uint32-array
+    description:
+      An array of child domain IDs that correspond to the power-domains
+      property. This property is only applicable to power domain providers
+      with "#power-domain-cells" > 0 (i.e., providers that supply multiple
+      power domains). It specifies which of the provider's child domains
+      should be associated with each parent domain listed in the power-domains
+      property. The number of elements in this array must match the number of
+      phandles in the power-domains property. Each element specifies the child
+      domain ID (index) that should be made a child domain of the corresponding
+      parent domain. This enables hierarchical power domain structures where
+      different child domains from the same provider can have different
+      parent domains.
+
 required:
   - "#power-domain-cells"
 
@@ -133,3 +148,22 @@ examples:
             min-residency-us = <7000>;
         };
     };
+
+  - |
+    // Example: SCMI domain 15 -> MAIN_PD, SCMI domain 19 -> WKUP_PD
+    MAIN_PD: power-controller-main {
+        compatible = "foo,power-controller";
+        #power-domain-cells = <0>;
+    };
+
+    WKUP_PD: power-controller-wkup {
+        compatible = "foo,power-controller";
+        #power-domain-cells = <0>;
+    };
+
+    scmi_pds: power-controller-scmi {
+        compatible = "foo,power-controller";
+        #power-domain-cells = <1>;
+        power-domains = <&MAIN_PD>, <&WKUP_PD>;
+        power-domains-child-ids = <15>, <19>;
+    };

-- 
2.51.0


^ permalink raw reply related

* [PATCH v2 0/3] pmdomain: core: add support for domain hierarchies in DT
From: Kevin Hilman (TI) @ 2026-04-10 23:44 UTC (permalink / raw)
  To: Ulf Hansson, Rob Herring
  Cc: Geert Uytterhoeven, linux-pm, devicetree, linux-kernel, arm-scmi,
	linux-arm-kernel

Currently, PM domains can only support hierarchy for simple
providers (e.g. ones with #power-domain-cells = 0).

Add support for oncell providers as well by adding a new property
`power-domains-child-ids` to describe the parent/child relationship.

Also adds the first user of the new API: the Arm SCMI PM domain driver.

Signed-off-by: Kevin Hilman (TI) <khilman@baylibre.com>
---
This idea was previously discussed on the arm-scmi mailing list[1]
where this approach was proposed by Ulf, and then an initial RFC[2]
implementation was made.  From there, it was suggested by Rob[3] to
use a nexus node map instead, which led to several more versions
attempting to implement that, culminating in v5[4], where Rob and
Geert then had second thoughts about the power-domain-map approach.

Therefore, I've gone back to the approach in the initial RFC[2] to use
the child-ids approach.

Changes compared to initial RFC[2]
- dropped RFC
- rewrote the parse/add function to use iterators/helpers from of.h
- add a remove function for cleanup
- use child domain language instead of subdomain

[1] https://lore.kernel.org/arm-scmi/CAPDyKFo_P129sVirHHYjOQT+QUmpymcRJme9obzKJeRgO7B-1A@mail.gmail.com/
[2] https://lore.kernel.org/all/20250528-pmdomain-hierarchy-onecell-v1-1-851780700c68@baylibre.com/
[3] https://lore.kernel.org/all/20250528203532.GA704342-robh@kernel.org/
[4] https://lore.kernel.org/r/20260122-pmdomain-hierarchy-onecell-v5-0-76855ec856bd@baylibre.com

Changes in v2:
- dt-bindings: fix warinings from make dt_binding_check
- scmi_pm_domain: switch to dev_err()
- pmdomain: core: fix locking around add/remove domains
- pmdomain: error unwind if any children fail to be added
- pmdomain: fix node reference leak
- pmdomain: ensure power-domains and child-ids properties are same
  length before iterating
- Link to v1: https://patch.msgid.link/20260310-topic-lpm-pmdomain-child-ids-v1-0-5361687a18ff@baylibre.com

---
Kevin Hilman (TI) (3):
      dt-bindings: power: Add power-domains-child-ids property
      pmdomain: core: add support for power-domains-child-ids
      pmdomain: arm_scmi: add support for domain hierarchies

 Documentation/devicetree/bindings/power/power-domain.yaml |  34 ++++++++++++++++++++++++++++++++++
 drivers/pmdomain/arm/scmi_pm_domain.c                     |  14 +++++++++++++-
 drivers/pmdomain/core.c                                   | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/pm_domain.h                                 |  16 ++++++++++++++++
 4 files changed, 229 insertions(+), 1 deletion(-)
---
base-commit: f7b88edb52c8dd01b7e576390d658ae6eef0e134
change-id: 20260310-topic-lpm-pmdomain-child-ids-e3d57ae57040

Best regards,
--  
Kevin Hilman (TI) <khilman@baylibre.com>


^ permalink raw reply

* Re: [GIT PULL] RISC-V SpacemiT Devicetrees for v7.1
From: Yixun Lan @ 2026-04-10 23:37 UTC (permalink / raw)
  To: Linus Walleij
  Cc: soc, Yixun Lan, Arnd Bergmann, spacemit, linux-riscv, devicetree,
	linux-kernel
In-Reply-To: <CAD++jLkQfUwhvDOCkhK2NtRyXrb6y9rwpDckR6h0G3+dzYwW3A@mail.gmail.com>

Hi Linus,

On 00:19 Sat 11 Apr     , Linus Walleij wrote:
> Hi Yixun,
> 
> I looked into this pull request.
> 
> I'm sorry if I do stupid mistakes in handling it, I'm new to maintaining
> the SoC tree. Bear with me.
> 
> On Fri, Apr 3, 2026 at 2:32 PM Yixun Lan <dlan@kernel.org> wrote:
> 
> > Aurelien Jarno (7):
> >       riscv: dts: spacemit: drop incorrect pinctrl for combo PHY
> (...)
> > Yixun Lan (9):
> >       riscv: dts: spacemit: pcie: fix missing power regulator
> 
> [Fixes]
> fatal: Not a valid object name linus/master
Could be the problem that your master branch isn't up-to-date?

> Commit: c68360c0d636 ("riscv: dts: spacemit: drop incorrect pinctrl
> for combo PHY")
..
>     Fixes tag: Fixes: 0be016a4b5d1b9 ("riscv: dts: spacemit: PCIe and
> PHY-related updates")
Above commit was merged for v7.0 cycle, while this PR is for v7.1
The fix isn't that critical and didn't cause any run-time issue, so
I do the fix in this v7.1 cycle..

>     Has these problem(s):
>         - Inspect: Target SHA is not ancestor of Linus' master branch,
> which means it is fixing commit in your branch
> fatal: Not a valid object name linus/master
> Commit: 8a9071299dec ("riscv: dts: spacemit: pcie: fix missing power regulator")
>     Fixes tag: Fixes: 0be016a4b5d1 ("riscv: dts: spacemit: PCIe and
> PHY-related updates")
>     Has these problem(s):
>         - Inspect: Target SHA is not ancestor of Linus' master branch,
> which means it is fixing commit in your branch
> 
> So this means you introduced bugs and fix them in the same pull request?
> 
> Why?
> 
> The practice is to squash such fixes into the offending patches when
> presenting pull requests. But I went ahead anyway, trying to not be so
> picky. (The commits are there, in your branch indeed.)
> 
Glad to learn this, I will keep it in mind..

> - Checked that it was in linux-next OK
Right, I think the commit is actually in v7.0-rc1

> - built DTBS OK
> 
> Pulled in, thanks.
Thank you

> 
> Yours,
> Linus Walleij

-- 
Yixun Lan (dlan)

^ permalink raw reply

* Re: [PATCH v7 0/8] hwmon: (ina3221) Various improvement and add support for SQ52210
From: Guenter Roeck @ 2026-04-10 23:30 UTC (permalink / raw)
  To: Wenliang Yan, Jean Delvare, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: Jonathan Corbet, linux-hwmon, devicetree, linux-kernel
In-Reply-To: <20260402081350.65559-1-wenliang202407@163.com>

Hi,

On 4/2/26 01:13, Wenliang Yan wrote:
> Changes in v7:
> - Fixed unnecessary semicolon in ina3221_read_value()
>    (reported by kernel test robot)
> 
> I will address any additional feedback in the next version.
> Thank you for your time
> 
Sashiko reports lots of issues with this series. Some of it is irrelevant
(for example, enum values for chip types are always lower case in the hwmon subsystem),
but many are real problems. Please take a look.

Thanks,
Guenter

> ---
> v6: https://lore.kernel.org/linux-hwmon/20260225090324.112145-1-wenliang202407@163.com/
> v5: https://lore.kernel.org/linux-hwmon/20260119121446.17469-1-wenliang202407@163.com/
> v4: https://lore.kernel.org/linux-hwmon/20260114081741.111340-1-wenliang202407@163.com/
> v3: https://lore.kernel.org/linux-hwmon/20251120081921.39412-1-wenliang202407@163.com/
> v2: https://lore.kernel.org/linux-hwmon/20251118125148.95603-1-wenliang202407@163.com/
> v1: https://lore.kernel.org/linux-hwmon/20251111080546.32421-1-wenliang202407@163.com/
> 
> Wenliang Yan (8):
>    dt-bindings: hwmon: ti,ina3221: Add SQ52210
>    hwmon: (ina3221) Add support for SQ52210
>    hwmon: (ina3221) Pre-calculate current and power LSB
>    hwmon: (ina3221) Support alert configuration
>    hwmon: (ina3221) Introduce power attribute and alert characteristics
>    hwmon: (ina3221) Modify the 'ina3221_read_value' function
>    hwmon: (ina3221) Support alert_limit_write function and write/read
>      functions for 'power' attribute
>    hwmon: (ina3221) Modify write/read functions for 'in' and 'curr'
>      attribute
> 
>   .../devicetree/bindings/hwmon/ti,ina3221.yaml |  15 +-
>   Documentation/hwmon/ina3221.rst               |  24 +
>   drivers/hwmon/ina3221.c                       | 548 +++++++++++++++++-
>   3 files changed, 571 insertions(+), 16 deletions(-)
> 


^ permalink raw reply

* Re: [PATCH v9 2/3] hwmon: ltc4283: Add support for the LTC4283 Swap Controller
From: Guenter Roeck @ 2026-04-10 23:27 UTC (permalink / raw)
  To: nuno.sa, linux-gpio, linux-hwmon, devicetree, linux-doc
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
	Linus Walleij, Bartosz Golaszewski
In-Reply-To: <20260406-ltc4283-support-v9-2-b66cfc749261@analog.com>

On 4/6/26 07:31, Nuno Sá via B4 Relay wrote:
> From: Nuno Sá <nuno.sa@analog.com>
> 
> Support the LTC4283 Hot Swap Controller. The device features programmable
> current limit with foldback and independently adjustable inrush current to
> optimize the MOSFET safe operating area (SOA). The SOA timer limits MOSFET
> temperature rise for reliable protection against overstresses.
> 
> An I2C interface and onboard ADC allow monitoring of board current,
> voltage, power, energy, and fault status.
> 
> Signed-off-by: Nuno Sá <nuno.sa@analog.com>

The patch still has some issues. Please see

https://sashiko.dev/#/patchset/20260406-ltc4283-support-v9-0-b66cfc749261%40analog.com

Specifically:

- regmap_clear_bits() may not cause problems, but it is not the best
   choice either because the register was already read.
   It might be better to just write the value to be masked since
   both the register value and the mask are known.

- I can't comment on the energy accuracy lost. That is your call.

- Clamping before multiplying is indeed wrong.
   You'll need to clamp before multiplying (and then possibly
   clamp again).

-  %*ph: The AI seems to have a point.

- debugfs: False positive. I'll need to check if the guidance ever made it into the
   Agent's prompts.

Thanks,
Guenter

> ---
>   Documentation/hwmon/index.rst   |    1 +
>   Documentation/hwmon/ltc4283.rst |  266 ++++++
>   MAINTAINERS                     |    1 +
>   drivers/hwmon/Kconfig           |   12 +
>   drivers/hwmon/Makefile          |    1 +
>   drivers/hwmon/ltc4283.c         | 1808 +++++++++++++++++++++++++++++++++++++++
>   6 files changed, 2089 insertions(+)
> 
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index 199f35a75282..d54dda83ab6e 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -144,6 +144,7 @@ Hardware Monitoring Kernel Drivers
>      ltc4260
>      ltc4261
>      ltc4282
> +   ltc4283
>      ltc4286
>      macsmc-hwmon
>      max127
> diff --git a/Documentation/hwmon/ltc4283.rst b/Documentation/hwmon/ltc4283.rst
> new file mode 100644
> index 000000000000..ba88445e45f4
> --- /dev/null
> +++ b/Documentation/hwmon/ltc4283.rst
> @@ -0,0 +1,266 @@
> +.. SPDX-License-Identifier: GPL-2.0-only
> +
> +Kernel drivers ltc4283
> +==========================================
> +
> +Supported chips:
> +
> +  * Analog Devices LTC4283
> +
> +    Prefix: 'ltc4283'
> +
> +    Addresses scanned: -
> +
> +    Datasheet:
> +
> +        https://www.analog.com/media/en/technical-documentation/data-sheets/ltc4283.pdf
> +
> +Author: Nuno Sá <nuno.sa@analog.com>
> +
> +Description
> +___________
> +
> +The LTC4283 negative voltage hot swap controller drives an external N-channel
> +MOSFET to allow a board to be safely inserted and removed from a live backplane.
> +The device features programmable current limit with foldback and independently
> +adjustable inrush current to optimize the MOSFET safe operating area (SOA). The
> +SOA timer limits MOSFET temperature rise for reliable protection against
> +overstresses. An I2C interface and onboard gear-shift ADC allow monitoring of
> +board current, voltage, power, energy, and fault status.  Additional features
> +respond to input UV/OV, interrupt the host when a fault has occurred, notify
> +when output power is good, detect insertion of a board, turn off the MOSFET
> +if an external supply monitor fails to indicate power good within a timeout
> +period, and auto-reboot after a programmable delay following a host commanded
> +turn-off.
> +
> +Sysfs entries
> +_____________
> +
> +The following attributes are supported. Limits are read-write and all the other
> +attributes are read-only. Note that the VADIOx channels might not be available
> +if the ADIO pins are used as GPIOs (naturally also affects the respective
> +differential channels).
> +
> +======================= ==========================================
> +in0_lcrit_alarm         Critical Undervoltage alarm
> +in0_crit_alarm          Critical Overvoltage alarm
> +in0_label		Channel label (VIN)
> +
> +in1_input		Output voltage (mV).
> +in1_min			Undervoltage threshold
> +in1_max			Overvoltage threshold
> +in1_lowest		Lowest measured voltage
> +in1_highest		Highest measured voltage
> +in1_reset_history	Write 1 to reset history.
> +in1_min_alarm		Undervoltage alarm
> +in1_max_alarm		Overvoltage alarm
> +in1_label		Channel label (VPWR)
> +
> +in2_input		Output voltage (mV).
> +in2_min			Undervoltage threshold
> +in2_max			Overvoltage threshold
> +in2_lowest		Lowest measured voltage
> +in2_highest		Highest measured voltage
> +in2_reset_history	Write 1 to reset history.
> +in2_min_alarm		Undervoltage alarm
> +in2_max_alarm		Overvoltage alarm
> +in2_enable		Enable/Disable monitoring.
> +in2_label		Channel label (VADI1)
> +
> +in3_input		Output voltage (mV).
> +in3_min			Undervoltage threshold
> +in3_max			Overvoltage threshold
> +in3_lowest		Lowest measured voltage
> +in3_highest		Highest measured voltage
> +in3_reset_history	Write 1 to reset history.
> +in3_min_alarm		Undervoltage alarm
> +in3_max_alarm		Overvoltage alarm
> +in3_enable		Enable/Disable monitoring.
> +in3_label		Channel label (VADI2)
> +
> +in4_input		Output voltage (mV).
> +in4_min			Undervoltage threshold
> +in4_max			Overvoltage threshold
> +in4_lowest		Lowest measured voltage
> +in4_highest		Highest measured voltage
> +in4_reset_history	Write 1 to reset history.
> +in4_min_alarm		Undervoltage alarm
> +in4_max_alarm		Overvoltage alarm
> +in4_enable		Enable/Disable monitoring.
> +in4_label		Channel label (VADI3)
> +
> +in5_input		Output voltage (mV).
> +in5_min			Undervoltage threshold
> +in5_max			Overvoltage threshold
> +in5_lowest		Lowest measured voltage
> +in5_highest		Highest measured voltage
> +in5_reset_history	Write 1 to reset history.
> +in5_min_alarm		Undervoltage alarm
> +in5_max_alarm		Overvoltage alarm
> +in5_enable		Enable/Disable monitoring.
> +in5_label		Channel label (VADI4)
> +
> +in6_input		Output voltage (mV).
> +in6_min			Undervoltage threshold
> +in6_max			Overvoltage threshold
> +in6_lowest		Lowest measured voltage
> +in6_highest		Highest measured voltage
> +in6_reset_history	Write 1 to reset history.
> +in6_min_alarm		Undervoltage alarm
> +in6_max_alarm		Overvoltage alarm
> +in6_enable		Enable/Disable monitoring.
> +in6_label		Channel label (VADIO1)
> +
> +in7_input		Output voltage (mV).
> +in7_min			Undervoltage threshold
> +in7_max			Overvoltage threshold
> +in7_lowest		Lowest measured voltage
> +in7_highest		Highest measured voltage
> +in7_reset_history	Write 1 to reset history.
> +in7_min_alarm		Undervoltage alarm
> +in7_max_alarm		Overvoltage alarm
> +in7_enable		Enable/Disable monitoring.
> +in7_label		Channel label (VADIO2)
> +
> +in8_input		Output voltage (mV).
> +in8_min			Undervoltage threshold
> +in8_max			Overvoltage threshold
> +in8_lowest		Lowest measured voltage
> +in8_highest		Highest measured voltage
> +in8_reset_history	Write 1 to reset history.
> +in8_min_alarm		Undervoltage alarm
> +in8_max_alarm		Overvoltage alarm
> +in8_enable		Enable/Disable monitoring.
> +in8_label		Channel label (VADIO3)
> +
> +in9_input		Output voltage (mV).
> +in9_min			Undervoltage threshold
> +in9_max			Overvoltage threshold
> +in9_lowest		Lowest measured voltage
> +in9_highest		Highest measured voltage
> +in9_reset_history	Write 1 to reset history.
> +in9_min_alarm		Undervoltage alarm
> +in9_max_alarm		Overvoltage alarm
> +in9_enable		Enable/Disable monitoring.
> +in9_label		Channel label (VADIO4)
> +
> +in10_input		Output voltage (mV).
> +in10_min		Undervoltage threshold
> +in10_max		Overvoltage threshold
> +in10_lowest		Lowest measured voltage
> +in10_highest		Highest measured voltage
> +in10_reset_history	Write 1 to reset history.
> +in10_min_alarm		Undervoltage alarm
> +in10_max_alarm		Overvoltage alarm
> +in10_enable		Enable/Disable monitoring.
> +in10_label		Channel label (DRNS)
> +
> +in11_input		Output voltage (mV).
> +in11_min		Undervoltage threshold
> +in11_max		Overvoltage threshold
> +in11_lowest		Lowest measured voltage
> +in11_highest		Highest measured voltage
> +in11_reset_history	Write 1 to reset history.
> +			Also clears fet bad and short fault logs.
> +in11_min_alarm		Undervoltage alarm
> +in11_max_alarm		Overvoltage alarm
> +in11_enable		Enable/Disable monitoring
> +in11_fault		Failure in the MOSFET. Either bad or shorted FET.
> +in11_label		Channel label (DRAIN)
> +
> +in12_input		Output voltage (mV).
> +in12_min		Undervoltage threshold
> +in12_max		Overvoltage threshold
> +in12_lowest		Lowest measured voltage
> +in12_highest		Highest measured voltage
> +in12_reset_history	Write 1 to reset history.
> +in12_min_alarm		Undervoltage alarm
> +in12_max_alarm		Overvoltage alarm
> +in12_enable		Enable/Disable monitoring.
> +in12_label		Channel label (ADIN2-ADIN1)
> +
> +in13_input		Output voltage (mV).
> +in13_min		Undervoltage threshold
> +in13_max		Overvoltage threshold
> +in13_lowest		Lowest measured voltage
> +in13_highest		Highest measured voltage
> +in13_reset_history	Write 1 to reset history.
> +in13_min_alarm		Undervoltage alarm
> +in13_max_alarm		Overvoltage alarm
> +in13_enable		Enable/Disable monitoring.
> +in13_label		Channel label (ADIN4-ADIN3)
> +
> +in14_input		Output voltage (mV).
> +in14_min		Undervoltage threshold
> +in14_max		Overvoltage threshold
> +in14_lowest		Lowest measured voltage
> +in14_highest		Highest measured voltage
> +in14_reset_history	Write 1 to reset history.
> +in14_min_alarm		Undervoltage alarm
> +in14_max_alarm		Overvoltage alarm
> +in14_enable		Enable/Disable monitoring.
> +in14_label		Channel label (ADIO2-ADIO1)
> +
> +in15_input		Output voltage (mV).
> +in15_min		Undervoltage threshold
> +in15_max		Overvoltage threshold
> +in15_lowest		Lowest measured voltage
> +in15_highest		Highest measured voltage
> +in15_reset_history	Write 1 to reset history.
> +in15_min_alarm		Undervoltage alarm
> +in15_max_alarm		Overvoltage alarm
> +in15_enable		Enable/Disable monitoring.
> +in15_label		Channel label (ADIO4-ADIO3)
> +
> +curr1_input		Sense current (mA)
> +curr1_min		Undercurrent threshold
> +curr1_max		Overcurrent threshold
> +curr1_lowest		Lowest measured current
> +curr1_highest		Highest measured current
> +curr1_reset_history	Write 1 to reset curr1 history.
> +			Also clears overcurrent fault logs.
> +curr1_min_alarm		Undercurrent alarm
> +curr1_max_alarm		Overcurrent alarm
> +curr1_crit_alarm        Critical Overcurrent alarm
> +curr1_label		Channel label (ISENSE)
> +
> +power1_input		Power (in uW)
> +power1_min		Low power threshold
> +power1_max		High power threshold
> +power1_input_lowest	Historical minimum power use
> +power1_input_highest	Historical maximum power use
> +power1_reset_history	Write 1 to reset power1 history.
> +			Also clears power fault logs.
> +power1_min_alarm	Low power alarm
> +power1_max_alarm	High power alarm
> +power1_label		Channel label (Power)
> +
> +energy1_input		Measured energy over time (in microJoule)
> +energy1_enable		Enable/Disable Energy accumulation
> +======================= ==========================================
> +
> +DebugFs entries
> +_______________
> +
> +The chip also has a fault log register where failures can be logged. Hence,
> +as these are logging events, we give access to them in debugfs. Note that
> +even if some failure is detected in these logs, it does necessarily mean
> +that the failure is still present. As mentioned in the proper Sysfs entries,
> +these logs can be cleared by writing in the proper reset_history attribute.
> +
> +.. warning:: The debugfs interface is subject to change without notice
> +             and is only available when the kernel is compiled with
> +             ``CONFIG_DEBUG_FS`` defined.
> +
> +``/sys/kernel/debug/i2c/i2c-[X]/[X]-addr/``
> +contains the following attributes:
> +
> +=======================		==========================================
> +power1_failed_fault_log		Set to 1 by a power1 fault occurring.
> +power1_good_input_fault_log	Set to 1 by a power1 good input fault occurring at PGIO3.
> +in11_fet_short_fault_log	Set to 1 when a FET-short fault occurs.
> +in11_fet_bad_fault_log		Set to 1 when a FET-BAD fault occurs.
> +in0_lcrit_fault_log		Set to 1 by a VIN undervoltage fault occurring.
> +in0_crit_fault_log		Set to 1 by a VIN overvoltage fault occurring.
> +curr1_crit_fault_log		Set to 1 by an overcurrent fault occurring.
> +======================= 	==========================================
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3f727d7fdfa4..a63833b6fe8b 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15166,6 +15166,7 @@ M:	Nuno Sá <nuno.sa@analog.com>
>   L:	linux-hwmon@vger.kernel.org
>   S:	Supported
>   F:	Documentation/devicetree/bindings/hwmon/adi,ltc4283.yaml
> +F:	drivers/hwmon/ltc4283.c
>   
>   LTC4286 HARDWARE MONITOR DRIVER
>   M:	Delphine CC Chiu <Delphine_CC_Chiu@Wiwynn.com>
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index fb847ab40ab4..4d9f500ae6ee 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -1157,6 +1157,18 @@ config SENSORS_LTC4282
>   	  This driver can also be built as a module. If so, the module will
>   	  be called ltc4282.
>   
> +config SENSORS_LTC4283
> +	tristate "Analog Devices LTC4283"
> +	depends on I2C
> +	select REGMAP_I2C
> +	select AUXILIARY_BUS
> +	help
> +	  If you say yes here you get support for Analog Devices LTC4283
> +	  Negative Voltage Hot Swap Controller I2C interface.
> +
> +	  This driver can also be built as a module. If so, the module will
> +	  be called ltc4283.
> +
>   config SENSORS_LTQ_CPUTEMP
>   	bool "Lantiq cpu temperature sensor driver"
>   	depends on SOC_XWAY
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 0fce31b43eb1..b9d7b0287b9c 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -147,6 +147,7 @@ obj-$(CONFIG_SENSORS_LTC4245)	+= ltc4245.o
>   obj-$(CONFIG_SENSORS_LTC4260)	+= ltc4260.o
>   obj-$(CONFIG_SENSORS_LTC4261)	+= ltc4261.o
>   obj-$(CONFIG_SENSORS_LTC4282)	+= ltc4282.o
> +obj-$(CONFIG_SENSORS_LTC4283)	+= ltc4283.o
>   obj-$(CONFIG_SENSORS_LTQ_CPUTEMP) += ltq-cputemp.o
>   obj-$(CONFIG_SENSORS_MACSMC_HWMON)	+= macsmc-hwmon.o
>   obj-$(CONFIG_SENSORS_MAX1111)	+= max1111.o
> diff --git a/drivers/hwmon/ltc4283.c b/drivers/hwmon/ltc4283.c
> new file mode 100644
> index 000000000000..2a2674a55167
> --- /dev/null
> +++ b/drivers/hwmon/ltc4283.c
> @@ -0,0 +1,1808 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Analog Devices LTC4283 I2C Negative Voltage Hot Swap Controller (HWMON)
> + *
> + * Copyright 2025 Analog Devices Inc.
> + */
> +#include <linux/auxiliary_bus.h>
> +#include <linux/bitfield.h>
> +#include <linux/bitmap.h>
> +#include <linux/bitops.h>
> +#include <linux/bits.h>
> +
> +#include <linux/debugfs.h>
> +#include <linux/device.h>
> +#include <linux/device/devres.h>
> +#include <linux/hwmon.h>
> +#include <linux/i2c.h>
> +#include <linux/math.h>
> +#include <linux/math64.h>
> +#include <linux/minmax.h>
> +#include <linux/module.h>
> +
> +#include <linux/mod_devicetable.h>
> +#include <linux/overflow.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +#include <linux/unaligned.h>
> +#include <linux/units.h>
> +
> +#define LTC4283_SYSTEM_STATUS		0x00
> +#define LTC4283_FAULT_STATUS		0x03
> +#define   LTC4283_OV_MASK		BIT(0)
> +#define   LTC4283_UV_MASK		BIT(1)
> +#define   LTC4283_OC_MASK		BIT(2)
> +#define   LTC4283_FET_BAD_MASK		BIT(3)
> +#define   LTC4283_FET_SHORT_MASK	BIT(6)
> +#define LTC4283_FAULT_LOG		0x04
> +#define   LTC4283_OV_FAULT_MASK		BIT(0)
> +#define   LTC4283_UV_FAULT_MASK		BIT(1)
> +#define   LTC4283_OC_FAULT_MASK		BIT(2)
> +#define   LTC4283_FET_BAD_FAULT_MASK	BIT(3)
> +#define   LTC4283_PGI_FAULT_MASK	BIT(4)
> +#define   LTC4283_PWR_FAIL_FAULT_MASK	BIT(5)
> +#define   LTC4283_FET_SHORT_FAULT_MASK	BIT(6)
> +#define LTC4283_ADC_ALM_LOG_1		0x05
> +#define   LTC4283_POWER_LOW_ALM		BIT(0)
> +#define   LTC4283_POWER_HIGH_ALM	BIT(1)
> +#define   LTC4283_SENSE_LOW_ALM		BIT(4)
> +#define   LTC4283_SENSE_HIGH_ALM	BIT(5)
> +#define LTC4283_ADC_ALM_LOG_2		0x06
> +#define LTC4283_ADC_ALM_LOG_3		0x07
> +#define LTC4283_ADC_ALM_LOG_4		0x08
> +#define LTC4283_ADC_ALM_LOG_5		0x09
> +#define LTC4283_CONTROL_1		0x0a
> +#define   LTC4283_RW_PAGE_MASK		BIT(0)
> +#define   LTC4283_PIGIO2_ACLB_MASK	BIT(2)
> +#define   LTC4283_PWRGD_RST_CTRL_MASK	BIT(3)
> +#define   LTC4283_FET_BAD_OFF_MASK	BIT(4)
> +#define   LTC4283_THERM_TMR_MASK	BIT(5)
> +#define   LTC4283_DVDT_MASK		BIT(6)
> +#define LTC4283_CONTROL_2		0x0b
> +#define   LTC4283_OV_RETRY_MASK		BIT(0)
> +#define   LTC4283_UV_RETRY_MASK		BIT(1)
> +#define   LTC4283_OC_RETRY_MASK		GENMASK(3, 2)
> +#define   LTC4283_FET_BAD_RETRY_MASK	GENMASK(5, 4)
> +#define   LTC4283_EXT_FAULT_RETRY_MASK	BIT(7)
> +#define LTC4283_RESERVED_OC		0x0c
> +#define LTC4283_CONFIG_1		0x0d
> +#define   LTC4283_FB_MASK		GENMASK(3, 2)
> +#define   LTC4283_ILIM_MASK		GENMASK(7, 4)
> +#define LTC4283_CONFIG_2		0x0e
> +#define   LTC4283_COOLING_DL_MASK	GENMASK(3, 1)
> +#define   LTC4283_FTBD_DL_MASK		GENMASK(5, 4)
> +#define LTC4283_CONFIG_3		0x0f
> +#define   LTC4283_VPWR_DRNS_MASK	BIT(6)
> +#define   LTC4283_EXTFLT_TURN_OFF_MASK	BIT(7)
> +#define LTC4283_PGIO_CONFIG		0x10
> +#define   LTC4283_PGIO1_CFG_MASK	GENMASK(1, 0)
> +#define   LTC4283_PGIO2_CFG_MASK	GENMASK(3, 2)
> +#define   LTC4283_PGIO3_CFG_MASK	GENMASK(5, 4)
> +#define   LTC4283_PGIO4_CFG_MASK	GENMASK(7, 6)
> +#define LTC4283_PGIO_CONFIG_2		0x11
> +#define   LTC4283_ADC_MASK		GENMASK(2, 0)
> +#define LTC4283_ADC_SELECT(c)		(0x13 + (c) / 8)
> +#define   LTC4283_ADC_SELECT_MASK(c)	BIT((c) % 8)
> +#define LTC4283_SENSE_MIN_TH		0x1b
> +#define LTC4283_SENSE_MAX_TH		0x1c
> +#define LTC4283_VPWR_MIN_TH		0x1d
> +#define LTC4283_VPWR_MAX_TH		0x1e
> +#define LTC4283_POWER_MIN_TH		0x1f
> +#define LTC4283_POWER_MAX_TH		0x20
> +#define LTC4283_ADC_2_MIN_TH(c)		(0x21 + (c) * 2)
> +#define LTC4283_ADC_2_MAX_TH(c)		(0x22 + (c) * 2)
> +#define LTC4283_ADC_2_MIN_TH_DIFF(c)	(0x39 + (c) * 2)
> +#define LTC4283_ADC_2_MAX_TH_DIFF(c)	(0x3a + (c) * 2)
> +#define LTC4283_SENSE			0x41
> +#define LTC4283_SENSE_MIN		0x42
> +#define LTC4283_SENSE_MAX		0x43
> +#define LTC4283_VPWR			0x44
> +#define LTC4283_VPWR_MIN		0x45
> +#define LTC4283_VPWR_MAX		0x46
> +#define LTC4283_POWER			0x47
> +#define LTC4283_POWER_MIN		0x48
> +#define LTC4283_POWER_MAX		0x49
> +#define LTC4283_RESERVED_68		0x68
> +#define LTC4283_RESERVED_6D		0x6D
> +/* get channels from ADC 2 */
> +#define LTC4283_ADC_2(c)		(0x4a + (c) * 3)
> +#define LTC4283_ADC_2_MIN(c)		(0x4b + (c) * 3)
> +#define LTC4283_ADC_2_MAX(c)		(0x4c + (c) * 3)
> +#define LTC4283_ADC_2_DIFF(c)		(0x6e + (c) * 3)
> +#define LTC4283_ADC_2_MIN_DIFF(c)	(0x6f + (c) * 3)
> +#define LTC4283_ADC_2_MAX_DIFF(c)	(0x70 + (c) * 3)
> +#define LTC4283_ENERGY			0x7a
> +#define LTC4283_METER_CONTROL		0x84
> +#define   LTC4283_INTEGRATE_I_MASK	BIT(0)
> +#define   LTC4283_METER_HALT_MASK	BIT(6)
> +#define LTC4283_RESERVED_86		0x86
> +#define LTC4283_RESERVED_8F		0x8F
> +#define LTC4283_FAULT_LOG_CTRL		0x90
> +#define   LTC4283_FAULT_LOG_EN_MASK	BIT(7)
> +#define LTC4283_RESERVED_91		0x91
> +#define LTC4283_RESERVED_A1		0xA1
> +#define LTC4283_RESERVED_A3		0xA3
> +#define LTC4283_RESERVED_AC		0xAC
> +#define LTC4283_POWER_PLAY_MSB		0xE7
> +#define LTC4283_POWER_PLAY_LSB		0xE8
> +#define LTC4283_RESERVED_F1		0xF1
> +#define LTC4283_RESERVED_FF		0xFF
> +
> +/* also applies for differential channels */
> +#define LTC4283_ADC1_FS_uV		32768
> +#define LTC4283_ADC2_FS_mV		2048
> +#define LTC4283_TCONV_uS		64103
> +#define LTC4283_VILIM_MIN_uV		15000
> +#define LTC4283_VILIM_MAX_uV		30000
> +#define LTC4283_VILIM_RANGE	\
> +	(LTC4283_VILIM_MAX_uV - LTC4283_VILIM_MIN_uV + 1)
> +
> +#define LTC4283_PGIO_FUNC_GPIO		2
> +#define LTC4283_PGIO2_FUNC_ACLB		3
> +
> +/*
> + * Maximum value for rsense in nano ohms. The reasoning for this value is that
> + * it's the max value for which multiplying by 256 does not overflow long on
> + * 32bits. For the minimum value, is a sane minimum rsense for which power_max
> + * does not overflow 32bits.
> + */
> +#define LTC4283_MAX_RSENSE	1677721599
> +#define LTC4283_MIN_RSENSE	50000
> +
> +/* voltage channels */
> +enum {
> +	LTC4283_CHAN_VIN,
> +	LTC4283_CHAN_VPWR,
> +	LTC4283_CHAN_ADI_1,
> +	LTC4283_CHAN_ADI_2,
> +	LTC4283_CHAN_ADI_3,
> +	LTC4283_CHAN_ADI_4,
> +	LTC4283_CHAN_ADIO_1,
> +	LTC4283_CHAN_ADIO_2,
> +	LTC4283_CHAN_ADIO_3,
> +	LTC4283_CHAN_ADIO_4,
> +	LTC4283_CHAN_DRNS,
> +	LTC4283_CHAN_DRAIN,
> +	/* differential channels */
> +	LTC4283_CHAN_ADIN12,
> +	LTC4283_CHAN_ADIN34,
> +	LTC4283_CHAN_ADIO12,
> +	LTC4283_CHAN_ADIO34,
> +	LTC4283_CHAN_MAX
> +};
> +
> +/* Just for ease of use on the regmap  */
> +#define LTC4283_ADIO34_MAX \
> +	LTC4283_ADC_2_MAX_DIFF(LTC4283_CHAN_ADIO34 - LTC4283_CHAN_ADIN12)
> +
> +struct ltc4283_hwmon {
> +	struct regmap *map;
> +	struct i2c_client *client;
> +	unsigned long gpio_mask;
> +	unsigned long ch_enable_mask;
> +	/* in microwatt */
> +	long power_max;
> +	/* in millivolt */
> +	u32 vsense_max;
> +	/* in tenths of microohm*/
> +	u32 rsense;
> +	bool energy_en;
> +	bool ext_fault;
> +};
> +
> +static int ltc4283_read_voltage_word(const struct ltc4283_hwmon *st,
> +				     u32 reg, u32 fs, long *val)
> +{
> +	unsigned int __raw;
> +	int ret;
> +
> +	ret = regmap_read(st->map, reg, &__raw);
> +	if (ret)
> +		return ret;
> +
> +	*val = DIV_ROUND_CLOSEST(__raw * fs, BIT(16));
> +	return 0;
> +}
> +
> +static int ltc4283_read_voltage_byte(const struct ltc4283_hwmon *st,
> +				     u32 reg, u32 fs, long *val)
> +{
> +	int ret;
> +	u32 in;
> +
> +	ret = regmap_read(st->map, reg, &in);
> +	if (ret)
> +		return ret;
> +
> +	*val = DIV_ROUND_CLOSEST(in * fs, BIT(8));
> +	return 0;
> +}
> +
> +static u32 ltc4283_in_reg(u32 attr, u32 channel)
> +{
> +	switch (attr) {
> +	case hwmon_in_input:
> +		if (channel == LTC4283_CHAN_VPWR)
> +			return LTC4283_VPWR;
> +		if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
> +			return LTC4283_ADC_2(channel - LTC4283_CHAN_ADI_1);
> +		return LTC4283_ADC_2_DIFF(channel - LTC4283_CHAN_ADIN12);
> +	case hwmon_in_highest:
> +		if (channel == LTC4283_CHAN_VPWR)
> +			return LTC4283_VPWR_MAX;
> +		if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
> +			return LTC4283_ADC_2_MAX(channel - LTC4283_CHAN_ADI_1);
> +		return LTC4283_ADC_2_MAX_DIFF(channel - LTC4283_CHAN_ADIN12);
> +	case hwmon_in_lowest:
> +		if (channel == LTC4283_CHAN_VPWR)
> +			return LTC4283_VPWR_MIN;
> +		if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
> +			return LTC4283_ADC_2_MIN(channel - LTC4283_CHAN_ADI_1);
> +		return LTC4283_ADC_2_MIN_DIFF(channel - LTC4283_CHAN_ADIN12);
> +	case hwmon_in_max:
> +		if (channel == LTC4283_CHAN_VPWR)
> +			return LTC4283_VPWR_MAX_TH;
> +		if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
> +			return LTC4283_ADC_2_MAX_TH(channel - LTC4283_CHAN_ADI_1);
> +		return LTC4283_ADC_2_MAX_TH_DIFF(channel - LTC4283_CHAN_ADIN12);
> +	default:
> +		if (channel == LTC4283_CHAN_VPWR)
> +			return LTC4283_VPWR_MIN_TH;
> +		if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
> +			return LTC4283_ADC_2_MIN_TH(channel - LTC4283_CHAN_ADI_1);
> +		return LTC4283_ADC_2_MIN_TH_DIFF(channel - LTC4283_CHAN_ADIN12);
> +	}
> +}
> +
> +static int ltc4283_read_in_vals(const struct ltc4283_hwmon *st,
> +				u32 attr, u32 channel, long *val)
> +{
> +	u32 reg = ltc4283_in_reg(attr, channel);
> +	int ret;
> +
> +	if (channel < LTC4283_CHAN_ADIN12) {
> +		if (attr != hwmon_in_max && attr != hwmon_in_min)
> +			return ltc4283_read_voltage_word(st, reg,
> +							 LTC4283_ADC2_FS_mV,
> +							 val);
> +
> +		return ltc4283_read_voltage_byte(st, reg,
> +						 LTC4283_ADC2_FS_mV, val);
> +	}
> +
> +	if (attr != hwmon_in_max && attr != hwmon_in_min)
> +		ret = ltc4283_read_voltage_word(st, reg,
> +						LTC4283_ADC1_FS_uV, val);
> +	else
> +		ret = ltc4283_read_voltage_byte(st, reg,
> +						LTC4283_ADC1_FS_uV, val);
> +	if (ret)
> +		return ret;
> +
> +	*val = DIV_ROUND_CLOSEST(*val, MILLI);
> +	return 0;
> +}
> +
> +static int ltc4283_read_alarm(struct ltc4283_hwmon *st, u32 reg,
> +			      u32 mask, long *val)
> +{
> +	u32 alarm;
> +	int ret;
> +
> +	ret = regmap_read(st->map, reg, &alarm);
> +	if (ret)
> +		return ret;
> +
> +	*val = !!(alarm & mask);
> +
> +	/* If not status/fault logs, clear the alarm after reading it. */
> +	if (reg != LTC4283_FAULT_STATUS && reg != LTC4283_FAULT_LOG)
> +		return regmap_clear_bits(st->map, reg, mask);
> +
> +	return 0;
> +}
> +
> +static int ltc4283_read_in_alarm(struct ltc4283_hwmon *st, u32 channel,
> +				 bool max_alm, long *val)
> +{
> +	if (channel == LTC4283_VPWR)
> +		return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
> +					  BIT(2 + max_alm), val);
> +
> +	if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_ADI_4) {
> +		u32 bit = (channel - LTC4283_CHAN_ADI_1) * 2;
> +		/*
> +		 * Lower channels go to higher bits. We also want to go +1 down
> +		 * in the min_alarm case.
> +		 */
> +		return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_2,
> +					  BIT(7 - bit - !max_alm), val);
> +	}
> +
> +	if (channel >= LTC4283_CHAN_ADIO_1 && channel <= LTC4283_CHAN_ADIO_4) {
> +		u32 bit = (channel - LTC4283_CHAN_ADIO_1) * 2;
> +
> +		return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_3,
> +					  BIT(7 - bit - !max_alm), val);
> +	}
> +
> +	if (channel >= LTC4283_CHAN_ADIN12 && channel <= LTC4283_CHAN_ADIO34) {
> +		u32 bit = (channel - LTC4283_CHAN_ADIN12) * 2;
> +
> +		return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_5,
> +					  BIT(7 - bit - !max_alm), val);
> +	}
> +
> +	if (channel == LTC4283_CHAN_DRNS)
> +		return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_4,
> +					  BIT(6 + max_alm), val);
> +
> +	return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_4, BIT(4 + max_alm),
> +				  val);
> +}
> +
> +static int ltc4283_read_in(struct ltc4283_hwmon *st, u32 attr, u32 channel,
> +			   long *val)
> +{
> +	switch (attr) {
> +	case hwmon_in_input:
> +		if (!test_bit(channel, &st->ch_enable_mask))
> +			return -ENODATA;
> +
> +		return ltc4283_read_in_vals(st, attr, channel, val);
> +	case hwmon_in_highest:
> +	case hwmon_in_lowest:
> +	case hwmon_in_max:
> +	case hwmon_in_min:
> +		return ltc4283_read_in_vals(st, attr, channel, val);
> +	case hwmon_in_max_alarm:
> +		return ltc4283_read_in_alarm(st, channel, true, val);
> +	case hwmon_in_min_alarm:
> +		return ltc4283_read_in_alarm(st, channel, false, val);
> +	case hwmon_in_crit_alarm:
> +		return ltc4283_read_alarm(st, LTC4283_FAULT_STATUS,
> +					  LTC4283_OV_MASK, val);
> +	case hwmon_in_lcrit_alarm:
> +		return ltc4283_read_alarm(st, LTC4283_FAULT_STATUS,
> +					  LTC4283_UV_MASK, val);
> +	case hwmon_in_fault:
> +		/*
> +		 * We report failure if we detect either a fer_bad or a
> +		 * fet_short in the status register.
> +		 */
> +		return ltc4283_read_alarm(st, LTC4283_FAULT_STATUS,
> +					  LTC4283_FET_BAD_MASK | LTC4283_FET_SHORT_MASK, val);
> +	case hwmon_in_enable:
> +		*val = test_bit(channel, &st->ch_enable_mask);
> +		return 0;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +	return 0;
> +}
> +
> +static int ltc4283_read_current_word(const struct ltc4283_hwmon *st, u32 reg,
> +				     long *val)
> +{
> +	u64 temp = (u64)LTC4283_ADC1_FS_uV * DECA * MILLI;
> +	unsigned int __raw;
> +	int ret;
> +
> +	ret = regmap_read(st->map, reg, &__raw);
> +	if (ret)
> +		return ret;
> +
> +	*val = DIV64_U64_ROUND_CLOSEST(__raw * temp,
> +				       BIT_ULL(16) * st->rsense);
> +
> +	return 0;
> +}
> +
> +static int ltc4283_read_current_byte(const struct ltc4283_hwmon *st, u32 reg,
> +				     long *val)
> +{
> +	u64 temp = (u64)LTC4283_ADC1_FS_uV * DECA * MILLI;
> +	u32 curr;
> +	int ret;
> +
> +	ret = regmap_read(st->map, reg, &curr);
> +	if (ret)
> +		return ret;
> +
> +	*val = DIV_ROUND_CLOSEST_ULL(curr * temp, BIT(8) * st->rsense);
> +	return 0;
> +}
> +
> +static int ltc4283_read_curr(struct ltc4283_hwmon *st, u32 attr, long *val)
> +{
> +	switch (attr) {
> +	case hwmon_curr_input:
> +		return ltc4283_read_current_word(st, LTC4283_SENSE, val);
> +	case hwmon_curr_highest:
> +		return ltc4283_read_current_word(st, LTC4283_SENSE_MAX, val);
> +	case hwmon_curr_lowest:
> +		return ltc4283_read_current_word(st, LTC4283_SENSE_MIN, val);
> +	case hwmon_curr_max:
> +		return ltc4283_read_current_byte(st, LTC4283_SENSE_MAX_TH, val);
> +	case hwmon_curr_min:
> +		return ltc4283_read_current_byte(st, LTC4283_SENSE_MIN_TH, val);
> +	case hwmon_curr_max_alarm:
> +		return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
> +					  LTC4283_SENSE_HIGH_ALM, val);
> +	case hwmon_curr_min_alarm:
> +		return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
> +					  LTC4283_SENSE_LOW_ALM, val);
> +	case hwmon_curr_crit_alarm:
> +		return ltc4283_read_alarm(st, LTC4283_FAULT_STATUS,
> +					  LTC4283_OC_MASK, val);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int ltc4283_read_power_word(const struct ltc4283_hwmon *st,
> +				   u32 reg, long *val)
> +{
> +	u64 temp = (u64)LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV * DECA * MILLI;
> +	unsigned int __raw;
> +	int ret;
> +
> +	ret = regmap_read(st->map, reg, &__raw);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * Power is given by:
> +	 *     P = CODE(16b) * 32.768mV * 2.048V / (2^16 * Rsense)
> +	 */
> +	*val = DIV64_U64_ROUND_CLOSEST(temp * __raw, BIT_ULL(16) * st->rsense);
> +
> +	return 0;
> +}
> +
> +static int ltc4283_read_power_byte(const struct ltc4283_hwmon *st,
> +				   u32 reg, long *val)
> +{
> +	u64 temp = (u64)LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV * DECA * MILLI;
> +	u32 power;
> +	int ret;
> +
> +	ret = regmap_read(st->map, reg, &power);
> +	if (ret)
> +		return ret;
> +
> +	*val = DIV_ROUND_CLOSEST_ULL(power * temp, BIT(8) * st->rsense);
> +
> +	return 0;
> +}
> +
> +static int ltc4283_read_power(struct ltc4283_hwmon *st, u32 attr, long *val)
> +{
> +	switch (attr) {
> +	case hwmon_power_input:
> +		return ltc4283_read_power_word(st, LTC4283_POWER, val);
> +	case hwmon_power_input_highest:
> +		return ltc4283_read_power_word(st, LTC4283_POWER_MAX, val);
> +	case hwmon_power_input_lowest:
> +		return ltc4283_read_power_word(st, LTC4283_POWER_MIN, val);
> +	case hwmon_power_max_alarm:
> +		return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
> +					  LTC4283_POWER_HIGH_ALM, val);
> +	case hwmon_power_min_alarm:
> +		return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
> +					  LTC4283_POWER_LOW_ALM, val);
> +	case hwmon_power_max:
> +		return ltc4283_read_power_byte(st, LTC4283_POWER_MAX_TH, val);
> +	case hwmon_power_min:
> +		return ltc4283_read_power_byte(st, LTC4283_POWER_MIN_TH, val);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int ltc4283_read_energy(struct ltc4283_hwmon *st, u32 attr, s64 *val)
> +{
> +	u64 temp = LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV, energy, temp_2;
> +	u8 raw[8] = {};
> +	int ret;
> +
> +	if (!st->energy_en)
> +		return -ENODATA;
> +
> +	ret = i2c_smbus_read_i2c_block_data(st->client, LTC4283_ENERGY, 6, raw);
> +	if (ret < 0)
> +		return ret;
> +	if (ret != 6)
> +		return -EIO;
> +
> +	energy = get_unaligned_be64(raw) >> 16;
> +
> +	/*
> +	 * The formula for energy is given by:
> +	 *	E = CODE(48b) * 32.768mV * 2.048V * Tconv / 2^24 * Rsense
> +	 *
> +	 * As Rsense can have tenths of micro-ohm resolution, we need to
> +	 * multiply by DECA to get microjoule.
> +	 */
> +	if (check_mul_overflow(temp * LTC4283_TCONV_uS, energy, &temp_2)) {
> +		/*
> +		 * We multiply again by 1000 to make sure that we don't get 0
> +		 * in the following division which could happen for big rsense
> +		 * values. OTOH, we then divide energy first by 1000 so that
> +		 * we do not overflow u64 again for very small rsense values.
> +		 * We add 100 factor for proper conversion to microjoule.
> +		 */
> +		temp_2 = DIV64_U64_ROUND_CLOSEST(temp * LTC4283_TCONV_uS * MILLI,
> +						 BIT_ULL(24) * st->rsense);
> +		energy = DIV_ROUND_CLOSEST_ULL(energy, MILLI * CENTI) * temp_2;
> +	} else {
> +		/* Put rsense back into nanoohm so we get microjoule. */
> +		energy = DIV64_U64_ROUND_CLOSEST(temp_2, BIT_ULL(24) * st->rsense * CENTI);
> +	}
> +
> +	*val = energy;
> +	return 0;
> +}
> +
> +static int ltc4283_read(struct device *dev, enum hwmon_sensor_types type,
> +			u32 attr, int channel, long *val)
> +{
> +	struct ltc4283_hwmon *st = dev_get_drvdata(dev);
> +
> +	switch (type) {
> +	case hwmon_in:
> +		return ltc4283_read_in(st, attr, channel, val);
> +	case hwmon_curr:
> +		return ltc4283_read_curr(st, attr, val);
> +	case hwmon_power:
> +		return ltc4283_read_power(st, attr, val);
> +	case hwmon_energy:
> +		*val = st->energy_en;
> +		return 0;
> +	case hwmon_energy64:
> +		return ltc4283_read_energy(st, attr, (s64 *)val);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int ltc4283_write_power_byte(const struct ltc4283_hwmon *st, u32 reg,
> +				    long val)
> +{
> +	u64 temp = (u64)LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV * DECA * MILLI;
> +	u32 __raw;
> +
> +	val = clamp_val(val, 0, st->power_max);
> +	__raw = DIV64_U64_ROUND_CLOSEST(val * BIT_ULL(8) * st->rsense, temp);
> +
> +	return regmap_write(st->map, reg, __raw);
> +}
> +
> +static int ltc4283_write_power_word(const struct ltc4283_hwmon *st,
> +				    u32 reg, long val)
> +{
> +	u64 temp = st->rsense * BIT_ULL(16), temp_2;
> +	u16 __raw;
> +
> +	if (check_mul_overflow(val, temp, &temp_2)) {
> +		temp = DIV_ROUND_CLOSEST_ULL(temp, DECA * MILLI);
> +		__raw = DIV_ROUND_CLOSEST_ULL(temp * val, LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV);
> +	} else {
> +		temp = (u64)LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV * DECA * MILLI;
> +		__raw = DIV64_U64_ROUND_CLOSEST(temp_2, temp);
> +	}
> +
> +	return regmap_write(st->map, reg, __raw);
> +}
> +
> +static int ltc4283_reset_power_hist(struct ltc4283_hwmon *st)
> +{
> +	int ret;
> +
> +	ret = ltc4283_write_power_word(st, LTC4283_POWER_MIN, st->power_max);
> +	if (ret)
> +		return ret;
> +
> +	ret = ltc4283_write_power_word(st, LTC4283_POWER_MAX, 0);
> +	if (ret)
> +		return ret;
> +
> +	/* Clear possible power faults. */
> +	return regmap_clear_bits(st->map, LTC4283_FAULT_LOG,
> +				 LTC4283_PWR_FAIL_FAULT_MASK | LTC4283_PGI_FAULT_MASK);
> +}
> +
> +static int ltc4283_write_power(struct ltc4283_hwmon *st, u32 attr, long val)
> +{
> +	switch (attr) {
> +	case hwmon_power_max:
> +		return ltc4283_write_power_byte(st, LTC4283_POWER_MAX_TH, val);
> +	case hwmon_power_min:
> +		return ltc4283_write_power_byte(st, LTC4283_POWER_MIN_TH, val);
> +	case hwmon_power_reset_history:
> +		return ltc4283_reset_power_hist(st);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int ltc4283_write_in_history(struct ltc4283_hwmon *st, u32 reg,
> +				    long lowest, u32 fs)
> +{
> +	u32 __raw;
> +	int ret;
> +
> +	__raw = DIV_ROUND_CLOSEST(BIT(16) * lowest, fs);
> +	if (__raw == BIT(16))
> +		__raw = U16_MAX;
> +
> +	ret = regmap_write(st->map, reg, __raw);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_write(st->map, reg + 1, 0);
> +}
> +
> +static int ltc4283_write_in_byte(const struct ltc4283_hwmon *st,
> +				 u32 reg, u32 fs, long val)
> +{
> +	u32 __raw;
> +
> +	val = clamp_val(val, 0, fs);
> +	__raw = DIV_ROUND_CLOSEST(val * BIT(8), fs);
> +	if (__raw == BIT(8))
> +		__raw = U8_MAX;
> +
> +	return regmap_write(st->map, reg, __raw);
> +}
> +
> +static int ltc4283_reset_in_hist(struct ltc4283_hwmon *st, u32 channel)
> +{
> +	u32 reg, fs;
> +	int ret;
> +
> +	/*
> +	 * Make sure to clear possible under/over voltage faults. Otherwise the
> +	 * chip won't latch on again.
> +	 */
> +	if (channel == LTC4283_CHAN_VIN)
> +		return regmap_clear_bits(st->map, LTC4283_FAULT_LOG,
> +					 LTC4283_OV_FAULT_MASK | LTC4283_UV_FAULT_MASK);
> +
> +	if (channel == LTC4283_CHAN_VPWR)
> +		return ltc4283_write_in_history(st, LTC4283_VPWR_MIN,
> +						LTC4283_ADC2_FS_mV,
> +						LTC4283_ADC2_FS_mV);
> +
> +	if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN) {
> +		fs = LTC4283_ADC2_FS_mV;
> +		reg = LTC4283_ADC_2_MIN(channel - LTC4283_CHAN_ADI_1);
> +	} else {
> +		fs = LTC4283_ADC1_FS_uV;
> +		reg = LTC4283_ADC_2_MIN_DIFF(channel - LTC4283_CHAN_ADIN12);
> +	}
> +
> +	ret = ltc4283_write_in_history(st, reg, fs, fs);
> +	if (ret)
> +		return ret;
> +	if (channel != LTC4283_CHAN_DRAIN)
> +		return 0;
> +
> +	/* Then, let's also clear possible fet faults. Same as above. */
> +	return regmap_clear_bits(st->map, LTC4283_FAULT_LOG,
> +				 LTC4283_FET_BAD_FAULT_MASK | LTC4283_FET_SHORT_FAULT_MASK);
> +}
> +
> +static int ltc4283_write_in_en(struct ltc4283_hwmon *st, u32 channel, bool en)
> +{
> +	unsigned int bit, adc_idx = channel - LTC4283_CHAN_ADI_1;
> +	unsigned int reg = LTC4283_ADC_SELECT(adc_idx);
> +	int ret;
> +
> +	bit = LTC4283_ADC_SELECT_MASK(adc_idx);
> +	if (channel > LTC4283_CHAN_DRAIN)
> +		/* Account for two reserved fields after DRAIN. */
> +		bit <<= 2;
> +
> +	if (en)
> +		ret = regmap_set_bits(st->map, reg, bit);
> +	else
> +		ret = regmap_clear_bits(st->map, reg, bit);
> +	if (ret)
> +		return ret;
> +
> +	__assign_bit(channel, &st->ch_enable_mask, en);
> +	return 0;
> +}
> +
> +static int ltc4283_write_minmax(struct ltc4283_hwmon *st, long val,
> +				u32 channel, bool is_max)
> +{
> +	u32 reg;
> +
> +	if (channel == LTC4283_CHAN_VPWR) {
> +		if (is_max)
> +			return ltc4283_write_in_byte(st, LTC4283_VPWR_MAX_TH,
> +						     LTC4283_ADC2_FS_mV, val);
> +
> +		return ltc4283_write_in_byte(st, LTC4283_VPWR_MIN_TH,
> +					     LTC4283_ADC2_FS_mV, val);
> +	}
> +
> +	if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN) {
> +		if (is_max) {
> +			reg = LTC4283_ADC_2_MAX_TH(channel - LTC4283_CHAN_ADI_1);
> +			return ltc4283_write_in_byte(st, reg,
> +						     LTC4283_ADC2_FS_mV, val);
> +		}
> +
> +		reg = LTC4283_ADC_2_MIN_TH(channel - LTC4283_CHAN_ADI_1);
> +		return ltc4283_write_in_byte(st, reg, LTC4283_ADC2_FS_mV, val);
> +	}
> +
> +	/* Just sanity check we do not overflow val for 32bit */
> +	val = clamp_val(val * MILLI, 0, LTC4283_ADC1_FS_uV);
> +
> +	if (is_max) {
> +		reg = LTC4283_ADC_2_MAX_TH_DIFF(channel - LTC4283_CHAN_ADIN12);
> +		return ltc4283_write_in_byte(st, reg, LTC4283_ADC1_FS_uV, val);
> +	}
> +
> +	reg = LTC4283_ADC_2_MIN_TH_DIFF(channel - LTC4283_CHAN_ADIN12);
> +	return ltc4283_write_in_byte(st, reg, LTC4283_ADC1_FS_uV, val);
> +}
> +
> +static int ltc4283_write_in(struct ltc4283_hwmon *st, u32 attr, long val,
> +			    int channel)
> +{
> +	switch (attr) {
> +	case hwmon_in_max:
> +		return ltc4283_write_minmax(st, val, channel, true);
> +	case hwmon_in_min:
> +		return ltc4283_write_minmax(st, val, channel, false);
> +	case hwmon_in_reset_history:
> +		return ltc4283_reset_in_hist(st, channel);
> +	case hwmon_in_enable:
> +		return ltc4283_write_in_en(st, channel, !!val);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int ltc4283_write_curr_byte(const struct ltc4283_hwmon *st,
> +				   u32 reg, long val)
> +{
> +	u32 temp = LTC4283_ADC1_FS_uV * DECA * MILLI;
> +	u32 reg_val, isense_max;
> +
> +	isense_max = DIV_ROUND_CLOSEST(st->vsense_max * MICRO * DECA, st->rsense);
> +	val = clamp_val(val, 0, isense_max);
> +	reg_val = DIV_ROUND_CLOSEST_ULL(val * BIT_ULL(8) * st->rsense, temp);
> +
> +	return regmap_write(st->map, reg, reg_val);
> +}
> +
> +static int ltc4283_write_curr_history(struct ltc4283_hwmon *st)
> +{
> +	int ret;
> +
> +	ret = ltc4283_write_in_history(st, LTC4283_SENSE_MIN,
> +				       st->vsense_max * MILLI,
> +				       LTC4283_ADC1_FS_uV);
> +	if (ret)
> +		return ret;
> +
> +	/* Now, let's also clear possible overcurrent logs. */
> +	return regmap_clear_bits(st->map, LTC4283_FAULT_LOG,
> +				 LTC4283_OC_FAULT_MASK);
> +}
> +
> +static int ltc4283_write_curr(struct ltc4283_hwmon *st, u32 attr, long val)
> +{
> +	switch (attr) {
> +	case hwmon_curr_max:
> +		return ltc4283_write_curr_byte(st, LTC4283_SENSE_MAX_TH, val);
> +	case hwmon_curr_min:
> +		return ltc4283_write_curr_byte(st, LTC4283_SENSE_MIN_TH, val);
> +	case hwmon_curr_reset_history:
> +		return ltc4283_write_curr_history(st);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int ltc4283_energy_enable_set(struct ltc4283_hwmon *st, long val)
> +{
> +	int ret;
> +
> +	/* Setting the bit halts the meter. */
> +	val = !!val;
> +	ret = regmap_update_bits(st->map, LTC4283_METER_CONTROL,
> +				 LTC4283_METER_HALT_MASK,
> +				 FIELD_PREP(LTC4283_METER_HALT_MASK, !val));
> +	if (ret)
> +		return ret;
> +
> +	st->energy_en = val;
> +
> +	return 0;
> +}
> +
> +static int ltc4283_write(struct device *dev, enum hwmon_sensor_types type,
> +			 u32 attr, int channel, long val)
> +{
> +	struct ltc4283_hwmon *st = dev_get_drvdata(dev);
> +
> +	switch (type) {
> +	case hwmon_power:
> +		return ltc4283_write_power(st, attr, val);
> +	case hwmon_in:
> +		return ltc4283_write_in(st, attr, val, channel);
> +	case hwmon_curr:
> +		return ltc4283_write_curr(st, attr, val);
> +	case hwmon_energy:
> +		return ltc4283_energy_enable_set(st, val);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static umode_t ltc4283_in_is_visible(const struct ltc4283_hwmon *st,
> +				     u32 attr, int channel)
> +{
> +	/* If ADIO is set as a GPIO, don´t make it visible. */
> +	if (channel >= LTC4283_CHAN_ADIO_1 && channel <= LTC4283_CHAN_ADIO_4) {
> +		/* ADIOX pins come at index 0 in the gpio mask. */
> +		channel -= LTC4283_CHAN_ADIO_1;
> +		if (test_bit(channel, &st->gpio_mask))
> +			return 0;
> +	}
> +
> +	/* Also take care of differential channels. */
> +	if (channel >= LTC4283_CHAN_ADIO12 && channel <= LTC4283_CHAN_ADIO34) {
> +		channel -= LTC4283_CHAN_ADIO12;
> +		/* If one channel in the pair is used, make it invisible. */
> +		if (test_bit(channel * 2, &st->gpio_mask) ||
> +		    test_bit(channel * 2 + 1, &st->gpio_mask))
> +			return 0;
> +	}
> +
> +	switch (attr) {
> +	case hwmon_in_input:
> +	case hwmon_in_highest:
> +	case hwmon_in_lowest:
> +	case hwmon_in_max_alarm:
> +	case hwmon_in_min_alarm:
> +	case hwmon_in_label:
> +	case hwmon_in_lcrit_alarm:
> +	case hwmon_in_crit_alarm:
> +	case hwmon_in_fault:
> +		return 0444;
> +	case hwmon_in_max:
> +	case hwmon_in_min:
> +	case hwmon_in_enable:
> +		return 0644;
> +	case hwmon_in_reset_history:
> +		return 0200;
> +	default:
> +		return 0;
> +	}
> +}
> +
> +static umode_t ltc4283_curr_is_visible(u32 attr)
> +{
> +	switch (attr) {
> +	case hwmon_curr_input:
> +	case hwmon_curr_highest:
> +	case hwmon_curr_lowest:
> +	case hwmon_curr_max_alarm:
> +	case hwmon_curr_min_alarm:
> +	case hwmon_curr_crit_alarm:
> +	case hwmon_curr_label:
> +		return 0444;
> +	case hwmon_curr_max:
> +	case hwmon_curr_min:
> +		return 0644;
> +	case hwmon_curr_reset_history:
> +		return 0200;
> +	default:
> +		return 0;
> +	}
> +}
> +
> +static umode_t ltc4283_power_is_visible(u32 attr)
> +{
> +	switch (attr) {
> +	case hwmon_power_input:
> +	case hwmon_power_input_highest:
> +	case hwmon_power_input_lowest:
> +	case hwmon_power_label:
> +	case hwmon_power_max_alarm:
> +	case hwmon_power_min_alarm:
> +		return 0444;
> +	case hwmon_power_max:
> +	case hwmon_power_min:
> +		return 0644;
> +	case hwmon_power_reset_history:
> +		return 0200;
> +	default:
> +		return 0;
> +	}
> +}
> +
> +static umode_t ltc4283_is_visible(const void *data,
> +				  enum hwmon_sensor_types type,
> +				  u32 attr, int channel)
> +{
> +	switch (type) {
> +	case hwmon_in:
> +		return ltc4283_in_is_visible(data, attr, channel);
> +	case hwmon_curr:
> +		return ltc4283_curr_is_visible(attr);
> +	case hwmon_power:
> +		return ltc4283_power_is_visible(attr);
> +	case hwmon_energy:
> +		/* hwmon_energy_enable */
> +		return 0644;
> +	case hwmon_energy64:
> +		/* hwmon_energy_input */
> +		return 0444;
> +	default:
> +		return 0;
> +	}
> +}
> +
> +static const char * const ltc4283_in_strs[] = {
> +	"VIN", "VPWR", "VADI1", "VADI2", "VADI3", "VADI4", "VADIO1", "VADIO2",
> +	"VADIO3", "VADIO4", "DRNS", "DRAIN", "ADIN2-ADIN1", "ADIN4-ADIN3",
> +	"ADIO2-ADIO1", "ADIO4-ADIO3"
> +};
> +
> +static int ltc4283_read_labels(struct device *dev,
> +			       enum hwmon_sensor_types type,
> +			       u32 attr, int channel, const char **str)
> +{
> +	switch (type) {
> +	case hwmon_in:
> +		*str = ltc4283_in_strs[channel];
> +		return 0;
> +	case hwmon_curr:
> +		*str = "ISENSE";
> +		return 0;
> +	case hwmon_power:
> +		*str = "Power";
> +		return 0;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +/*
> + * Set max limits for ISENSE and Power as that depends on the max voltage on
> + * rsense that is defined in ILIM_ADJUST. This is specially important for power
> + * because for some rsense and vfsout values, if we allow the default raw 255
> + * value, that would overflow long in 32bit archs when reading back the max
> + * power limit.
> + */
> +static int ltc4283_set_max_limits(struct ltc4283_hwmon *st, struct device *dev)
> +{
> +	u32 temp = st->vsense_max * DECA * MICRO;
> +	int ret;
> +
> +	ret = ltc4283_write_in_byte(st, LTC4283_SENSE_MAX_TH, LTC4283_ADC1_FS_uV,
> +				    st->vsense_max * MILLI);
> +	if (ret)
> +		return ret;
> +
> +	/* Power is given by ISENSE * Vout. */
> +	st->power_max = DIV_ROUND_CLOSEST(temp, st->rsense) * LTC4283_ADC2_FS_mV;
> +	return ltc4283_write_power_byte(st, LTC4283_POWER_MAX_TH, st->power_max);
> +}
> +
> +static int ltc4283_parse_array_prop(const struct ltc4283_hwmon *st,
> +				    struct device *dev, const char *prop,
> +				    const u32 *vals, u32 n_vals)
> +{
> +	u32 prop_val;
> +	int ret;
> +	u32 i;
> +
> +	ret = device_property_read_u32(dev, prop, &prop_val);
> +	if (ret)
> +		return n_vals;
> +
> +	for (i = 0; i < n_vals; i++) {
> +		if (prop_val != vals[i])
> +			continue;
> +
> +		return i;
> +	}
> +
> +	return dev_err_probe(dev, -EINVAL,
> +			     "Invalid %s property value %u, expected one of: %*ph\n",
> +			     prop, prop_val, n_vals, vals);
> +}
> +
> +static int ltc4283_get_defaults(struct ltc4283_hwmon *st)
> +{
> +	u32 reg_val, ilm_adjust, c;
> +	int ret;
> +
> +	ret = regmap_read(st->map, LTC4283_METER_CONTROL, &reg_val);
> +	if (ret)
> +		return ret;
> +
> +	st->energy_en = !FIELD_GET(LTC4283_METER_HALT_MASK, reg_val);
> +
> +	ret = regmap_read(st->map, LTC4283_CONFIG_1, &reg_val);
> +	if (ret)
> +		return ret;
> +
> +	ilm_adjust = FIELD_GET(LTC4283_ILIM_MASK, reg_val);
> +	st->vsense_max = LTC4283_VILIM_MIN_uV / MILLI + ilm_adjust;
> +
> +	ret = regmap_read(st->map, LTC4283_PGIO_CONFIG, &reg_val);
> +	if (ret)
> +		return ret;
> +
> +	/* Can be latter overwritten in ltc4283_pgio_config() */
> +	if (FIELD_GET(LTC4283_PGIO4_CFG_MASK, reg_val) < LTC4283_PGIO_FUNC_GPIO)
> +		st->ext_fault = true;
> +
> +	/* VPWR and VIN are always enabled */
> +	__set_bit(LTC4283_CHAN_VIN, &st->ch_enable_mask);
> +	__set_bit(LTC4283_CHAN_VPWR, &st->ch_enable_mask);
> +	for (c = LTC4283_CHAN_ADI_1; c < LTC4283_CHAN_MAX; c++) {
> +		u32 chan = c - LTC4283_CHAN_ADI_1, bit;
> +
> +		ret = regmap_read(st->map, LTC4283_ADC_SELECT(chan), &reg_val);
> +		if (ret)
> +			return ret;
> +
> +		bit = LTC4283_ADC_SELECT_MASK(chan);
> +		if (c > LTC4283_CHAN_DRAIN)
> +			/* account for two reserved fields after DRAIN */
> +			bit <<= 2;
> +
> +		if (!(bit & reg_val))
> +			continue;
> +
> +		__set_bit(c, &st->ch_enable_mask);
> +	}
> +
> +	return 0;
> +}
> +
> +static const char * const ltc4283_pgio1_funcs[] = {
> +	"inverted_power_good", "power_good", "gpio"
> +};
> +
> +static const char * const ltc4283_pgio2_funcs[] = {
> +	 "inverted_power_good", "power_good", "gpio", "active_current_limiting"
> +};
> +
> +static const char * const ltc4283_pgio3_funcs[] = {
> +	"inverted_power_good_input", "power_good_input", "gpio"
> +};
> +
> +static const char * const ltc4283_pgio4_funcs[] = {
> +	"inverted_external_fault", "external_fault", "gpio"
> +};
> +
> +enum {
> +	LTC4283_PIN_ADIO1,
> +	LTC4283_PIN_ADIO2,
> +	LTC4283_PIN_ADIO3,
> +	LTC4283_PIN_ADIO4,
> +	LTC4283_PIN_PGIO1,
> +	LTC4283_PIN_PGIO2,
> +	LTC4283_PIN_PGIO3,
> +	LTC4283_PIN_PGIO4,
> +};
> +
> +static int ltc4283_pgio_config(struct ltc4283_hwmon *st, struct device *dev)
> +{
> +	int ret, func;
> +
> +	func = device_property_match_property_string(dev, "adi,pgio1-func",
> +						     ltc4283_pgio1_funcs,
> +						     ARRAY_SIZE(ltc4283_pgio1_funcs));
> +	if (func < 0 && func != -EINVAL)
> +		return dev_err_probe(dev, func,
> +				     "Invalid adi,pgio1-func property\n");
> +	if (func >= 0) {
> +		if (func == LTC4283_PGIO_FUNC_GPIO) {
> +			__set_bit(LTC4283_PIN_PGIO1, &st->gpio_mask);
> +			/* If GPIO, default to an input pin. */
> +			func++;
> +		}
> +
> +		ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG,
> +					 LTC4283_PGIO1_CFG_MASK,
> +					 FIELD_PREP(LTC4283_PGIO1_CFG_MASK, func));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	func = device_property_match_property_string(dev, "adi,pgio2-func",
> +						     ltc4283_pgio2_funcs,
> +						     ARRAY_SIZE(ltc4283_pgio2_funcs));
> +
> +	if (func < 0 && func != -EINVAL)
> +		return dev_err_probe(dev, func,
> +				     "Invalid adi,pgio2-func property\n");
> +	if (func >= 0) {
> +		if (func != LTC4283_PGIO2_FUNC_ACLB) {
> +			if (func == LTC4283_PGIO_FUNC_GPIO)  {
> +				__set_bit(LTC4283_PIN_PGIO2, &st->gpio_mask);
> +				func++;
> +			}
> +
> +			ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG,
> +						 LTC4283_PGIO2_CFG_MASK,
> +						 FIELD_PREP(LTC4283_PGIO2_CFG_MASK, func));
> +		} else {
> +			ret = regmap_set_bits(st->map, LTC4283_CONTROL_1,
> +					      LTC4283_PIGIO2_ACLB_MASK);
> +		}
> +
> +		if (ret)
> +			return ret;
> +	}
> +
> +	func = device_property_match_property_string(dev, "adi,pgio3-func",
> +						     ltc4283_pgio3_funcs,
> +						     ARRAY_SIZE(ltc4283_pgio3_funcs));
> +
> +	if (func < 0 && func != -EINVAL)
> +		return dev_err_probe(dev, func,
> +				     "Invalid adi,pgio3-func property\n");
> +	if (func >= 0) {
> +		if (func == LTC4283_PGIO_FUNC_GPIO) {
> +			__set_bit(LTC4283_PIN_PGIO3, &st->gpio_mask);
> +			func++;
> +		}
> +
> +		ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG,
> +					 LTC4283_PGIO3_CFG_MASK,
> +					 FIELD_PREP(LTC4283_PGIO3_CFG_MASK, func));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	func = device_property_match_property_string(dev, "adi,pgio4-func",
> +						     ltc4283_pgio4_funcs,
> +						     ARRAY_SIZE(ltc4283_pgio4_funcs));
> +
> +	if (func < 0 && func != -EINVAL)
> +		return dev_err_probe(dev, func,
> +				     "Invalid adi,pgio4-func property\n");
> +	if (func >= 0) {
> +		if (func == LTC4283_PGIO_FUNC_GPIO) {
> +			__set_bit(LTC4283_PIN_PGIO4, &st->gpio_mask);
> +			func++;
> +			st->ext_fault = false;
> +		} else {
> +			st->ext_fault = true;
> +		}
> +
> +		ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG,
> +					 LTC4283_PGIO4_CFG_MASK,
> +					 FIELD_PREP(LTC4283_PGIO4_CFG_MASK, func));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ltc4283_adio_config(struct ltc4283_hwmon *st, struct device *dev,
> +			       const char *prop, u32 pin)
> +{
> +	u32 adc_idx;
> +	int ret;
> +
> +	if (!device_property_read_bool(dev, prop))
> +		return 0;
> +
> +	adc_idx = LTC4283_CHAN_ADIO_1 - LTC4283_CHAN_ADI_1 + pin;
> +	ret = regmap_clear_bits(st->map, LTC4283_ADC_SELECT(adc_idx),
> +				LTC4283_ADC_SELECT_MASK(adc_idx));
> +	if (ret)
> +		return ret;
> +
> +	__set_bit(pin, &st->gpio_mask);
> +	return 0;
> +}
> +
> +static int ltc4283_pin_config(struct ltc4283_hwmon *st, struct device *dev)
> +{
> +	int ret;
> +
> +	ret = ltc4283_pgio_config(st, dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = ltc4283_adio_config(st, dev, "adi,gpio-on-adio1", LTC4283_PIN_ADIO1);
> +	if (ret)
> +		return ret;
> +
> +	ret = ltc4283_adio_config(st, dev, "adi,gpio-on-adio2", LTC4283_PIN_ADIO2);
> +	if (ret)
> +		return ret;
> +
> +	ret = ltc4283_adio_config(st, dev, "adi,gpio-on-adio3", LTC4283_PIN_ADIO3);
> +	if (ret)
> +		return ret;
> +
> +	return ltc4283_adio_config(st, dev, "adi,gpio-on-adio4", LTC4283_PIN_ADIO4);
> +}
> +
> +static const char * const ltc4283_oc_fet_retry[] = {
> +	"latch-off", "1", "7", "unlimited"
> +};
> +
> +static const u32 ltc4283_fb_factor[] = {
> +	100, 50, 20, 10
> +};
> +
> +static const u32 ltc4283_cooling_dl[] = {
> +	512, 1002, 2005, 4100, 8190, 16400, 32800, 65600
> +};
> +
> +static const u32 ltc4283_fet_bad_delay[] = {
> +	256, 512, 1002, 2005
> +};
> +
> +static int ltc4283_setup(struct ltc4283_hwmon *st, struct device *dev)
> +{
> +	u32 val;
> +	int ret;
> +
> +	/* The part has an eeprom so let's get the needed defaults from it */
> +	ret = ltc4283_get_defaults(st);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * Default to LTC4283_MIN_RSENSE so we can probe without FW properties.
> +	 */
> +	st->rsense = LTC4283_MIN_RSENSE;
> +	ret = device_property_read_u32(dev, "adi,rsense-nano-ohms",
> +				       &st->rsense);
> +	if (!ret) {
> +		if (st->rsense < LTC4283_MIN_RSENSE || st->rsense > LTC4283_MAX_RSENSE)
> +			return dev_err_probe(dev, -EINVAL,
> +					     "adi,rsense-nano-ohms(%u) too small or too large [%u %u]\n",
> +					     st->rsense, LTC4283_MIN_RSENSE, LTC4283_MAX_RSENSE);
> +	}
> +
> +	/*
> +	 * The resolution for rsense is tenths of micro (eg: 62.5 uOhm) which
> +	 * means we need nano in the bindings. However, to make things easier to
> +	 * handle (with respect to overflows) we divide it by 100 as we don't
> +	 * really need the last two digits.
> +	 */
> +	st->rsense /= CENTI;
> +
> +	ret = device_property_read_u32(dev, "adi,current-limit-sense-microvolt",
> +				       &st->vsense_max);
> +	if (!ret) {
> +		u32 reg_val;
> +
> +		if (!in_range(st->vsense_max, LTC4283_VILIM_MIN_uV,
> +			      LTC4283_VILIM_RANGE)) {
> +			return dev_err_probe(dev, -EINVAL,
> +					     "adi,current-limit-sense-microvolt (%u) out of range [%u %u]\n",
> +					     st->vsense_max, LTC4283_VILIM_MIN_uV,
> +					     LTC4283_VILIM_MAX_uV);
> +		}
> +
> +		st->vsense_max /= MILLI;
> +		reg_val = FIELD_PREP(LTC4283_ILIM_MASK,
> +				     st->vsense_max - LTC4283_VILIM_MIN_uV / MILLI);
> +		ret = regmap_update_bits(st->map, LTC4283_CONFIG_1,
> +					 LTC4283_ILIM_MASK, reg_val);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = ltc4283_parse_array_prop(st, dev, "adi,current-limit-foldback-factor",
> +				       ltc4283_fb_factor, ARRAY_SIZE(ltc4283_fb_factor));
> +	if (ret < 0)
> +		return ret;
> +	if (ret < ARRAY_SIZE(ltc4283_fb_factor)) {
> +		ret = regmap_update_bits(st->map, LTC4283_CONFIG_1, LTC4283_FB_MASK,
> +					 FIELD_PREP(LTC4283_FB_MASK, ret));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = ltc4283_parse_array_prop(st, dev, "adi,cooling-delay-ms",
> +				       ltc4283_cooling_dl, ARRAY_SIZE(ltc4283_cooling_dl));
> +	if (ret < 0)
> +		return ret;
> +	if (ret < ARRAY_SIZE(ltc4283_cooling_dl)) {
> +		ret = regmap_update_bits(st->map, LTC4283_CONFIG_2, LTC4283_COOLING_DL_MASK,
> +					 FIELD_PREP(LTC4283_COOLING_DL_MASK, ret));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = ltc4283_parse_array_prop(st, dev, "adi,fet-bad-timer-delay-ms",
> +				       ltc4283_fet_bad_delay, ARRAY_SIZE(ltc4283_fet_bad_delay));
> +	if (ret < 0)
> +		return ret;
> +	if (ret < ARRAY_SIZE(ltc4283_fet_bad_delay)) {
> +		ret = regmap_update_bits(st->map, LTC4283_CONFIG_2, LTC4283_FTBD_DL_MASK,
> +					 FIELD_PREP(LTC4283_FTBD_DL_MASK, ret));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = ltc4283_set_max_limits(st, dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = ltc4283_pin_config(st, dev);
> +	if (ret)
> +		return ret;
> +
> +	if (device_property_read_bool(dev, "adi,power-good-reset-on-fet")) {
> +		ret = regmap_clear_bits(st->map, LTC4283_CONTROL_1,
> +					LTC4283_PWRGD_RST_CTRL_MASK);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (device_property_read_bool(dev, "adi,fet-turn-off-disable")) {
> +		ret = regmap_clear_bits(st->map, LTC4283_CONTROL_1,
> +					LTC4283_FET_BAD_OFF_MASK);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (device_property_read_bool(dev, "adi,tmr-pull-down-disable")) {
> +		ret = regmap_set_bits(st->map, LTC4283_CONTROL_1,
> +				      LTC4283_THERM_TMR_MASK);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (device_property_read_bool(dev, "adi,dvdt-inrush-control-disable")) {
> +		ret = regmap_clear_bits(st->map, LTC4283_CONTROL_1,
> +					LTC4283_DVDT_MASK);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (device_property_read_bool(dev, "adi,undervoltage-retry-disable")) {
> +		ret = regmap_clear_bits(st->map, LTC4283_CONTROL_2,
> +					LTC4283_UV_RETRY_MASK);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (device_property_read_bool(dev, "adi,overvoltage-retry-disable")) {
> +		ret = regmap_clear_bits(st->map, LTC4283_CONTROL_2,
> +					LTC4283_OV_RETRY_MASK);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (device_property_read_bool(dev, "adi,external-fault-retry-enable")) {
> +		if (!st->ext_fault)
> +			return dev_err_probe(dev, -EINVAL,
> +					     "adi,external-fault-retry-enable set but PGIO4 not configured\n");
> +		ret = regmap_set_bits(st->map, LTC4283_CONTROL_2,
> +				      LTC4283_EXT_FAULT_RETRY_MASK);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (device_property_read_bool(dev, "adi,fault-log-enable")) {
> +		ret = regmap_set_bits(st->map, LTC4283_FAULT_LOG_CTRL,
> +				      LTC4283_FAULT_LOG_EN_MASK);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = device_property_match_property_string(dev, "adi,overcurrent-retries",
> +						    ltc4283_oc_fet_retry,
> +						    ARRAY_SIZE(ltc4283_oc_fet_retry));
> +	/* We still want to catch when an invalid string is given. */
> +	if (ret < 0 && ret != -EINVAL)
> +		return dev_err_probe(dev, ret,
> +				     "adi,overcurrent-retries invalid value\n");
> +	if (ret >= 0) {
> +		ret = regmap_update_bits(st->map, LTC4283_CONTROL_2,
> +					 LTC4283_OC_RETRY_MASK,
> +					 FIELD_PREP(LTC4283_OC_RETRY_MASK, ret));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = device_property_match_property_string(dev, "adi,fet-bad-retries",
> +						    ltc4283_oc_fet_retry,
> +						    ARRAY_SIZE(ltc4283_oc_fet_retry));
> +	if (ret < 0 && ret != -EINVAL)
> +		return dev_err_probe(dev, ret,
> +				     "adi,fet-bad-retries invalid value\n");
> +	if (ret >= 0) {
> +		ret = regmap_update_bits(st->map, LTC4283_CONTROL_2,
> +					 LTC4283_FET_BAD_RETRY_MASK,
> +					 FIELD_PREP(LTC4283_FET_BAD_RETRY_MASK, ret));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (device_property_read_bool(dev, "adi,external-fault-fet-off-enable")) {
> +		if (!st->ext_fault)
> +			return dev_err_probe(dev, -EINVAL,
> +					     "adi,external-fault-fet-off-enable set but PGIO4 not configured\n");
> +		ret = regmap_set_bits(st->map, LTC4283_CONFIG_3,
> +				      LTC4283_EXTFLT_TURN_OFF_MASK);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (device_property_read_bool(dev, "adi,vpower-drns-enable")) {
> +		u32 chan = LTC4283_CHAN_DRNS - LTC4283_CHAN_ADI_1;
> +
> +		__clear_bit(LTC4283_CHAN_DRNS, &st->ch_enable_mask);
> +		/*
> +		 * Then, let's by default disable DRNS from ADC2 given that it
> +		 * is already being monitored by the VPWR channel. One can still
> +		 * enable it later on if needed.
> +		 */
> +		ret = regmap_clear_bits(st->map, LTC4283_ADC_SELECT(chan),
> +					LTC4283_ADC_SELECT_MASK(chan));
> +		if (ret)
> +			return ret;
> +
> +		val = 1;
> +	} else {
> +		val = 0;
> +	}
> +
> +	ret = regmap_update_bits(st->map, LTC4283_CONFIG_3,
> +				 LTC4283_VPWR_DRNS_MASK,
> +				 FIELD_PREP(LTC4283_VPWR_DRNS_MASK, val));
> +	if (ret)
> +		return ret;
> +
> +	/* Make sure the ADC has 12bit resolution since we're assuming that. */
> +	ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG_2,
> +				 LTC4283_ADC_MASK,
> +				 FIELD_PREP(LTC4283_ADC_MASK, 3));
> +	if (ret)
> +		return ret;
> +
> +	/* Energy reads (which are 6 byte block reads) rely on page access */
> +	ret = regmap_set_bits(st->map, LTC4283_CONTROL_1, LTC4283_RW_PAGE_MASK);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * Make sure we are integrating power as we only support reporting
> +	 * consumed energy.
> +	 */
> +	return regmap_clear_bits(st->map, LTC4283_METER_CONTROL,
> +				 LTC4283_INTEGRATE_I_MASK);
> +}
> +
> +static const struct hwmon_channel_info * const ltc4283_info[] = {
> +	HWMON_CHANNEL_INFO(in,
> +			   HWMON_I_LCRIT_ALARM | HWMON_I_CRIT_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_MAX_ALARM | HWMON_I_RESET_HISTORY |
> +			   HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_FAULT | HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL),
> +	HWMON_CHANNEL_INFO(curr,
> +			   HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST |
> +			   HWMON_C_MAX | HWMON_C_MIN | HWMON_C_MIN_ALARM |
> +			   HWMON_C_MAX_ALARM | HWMON_C_CRIT_ALARM |
> +			   HWMON_C_RESET_HISTORY | HWMON_C_LABEL),
> +	HWMON_CHANNEL_INFO(power,
> +			   HWMON_P_INPUT | HWMON_P_INPUT_LOWEST |
> +			   HWMON_P_INPUT_HIGHEST | HWMON_P_MAX | HWMON_P_MIN |
> +			   HWMON_P_MAX_ALARM | HWMON_P_MIN_ALARM |
> +			   HWMON_P_RESET_HISTORY | HWMON_P_LABEL),
> +	HWMON_CHANNEL_INFO(energy,
> +			   HWMON_E_ENABLE),
> +	HWMON_CHANNEL_INFO(energy64,
> +			   HWMON_E_INPUT),
> +	NULL
> +};
> +
> +static const struct hwmon_ops ltc4283_ops = {
> +	.read = ltc4283_read,
> +	.write = ltc4283_write,
> +	.is_visible = ltc4283_is_visible,
> +	.read_string = ltc4283_read_labels,
> +};
> +
> +static const struct hwmon_chip_info ltc4283_chip_info = {
> +	.ops = &ltc4283_ops,
> +	.info = ltc4283_info,
> +};
> +
> +static int ltc4283_show_fault_log(void *arg, u64 *val, u32 mask)
> +{
> +	struct ltc4283_hwmon *st = arg;
> +	long alarm;
> +	int ret;
> +
> +	ret = ltc4283_read_alarm(st, LTC4283_FAULT_LOG, mask, &alarm);
> +	if (ret)
> +		return ret;
> +
> +	*val = alarm;
> +
> +	return 0;
> +}
> +
> +static int ltc4283_show_in0_lcrit_fault_log(void *arg, u64 *val)
> +{
> +	return ltc4283_show_fault_log(arg, val, LTC4283_UV_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_in0_lcrit_fault_log,
> +			 ltc4283_show_in0_lcrit_fault_log, NULL, "%llu\n");
> +
> +static int ltc4283_show_in0_crit_fault_log(void *arg, u64 *val)
> +{
> +	return ltc4283_show_fault_log(arg, val, LTC4283_OV_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_in0_crit_fault_log,
> +			 ltc4283_show_in0_crit_fault_log, NULL, "%llu\n");
> +
> +static int ltc4283_show_fet_bad_fault_log(void *arg, u64 *val)
> +{
> +	return ltc4283_show_fault_log(arg, val, LTC4283_FET_BAD_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_fet_bad_fault_log,
> +			 ltc4283_show_fet_bad_fault_log, NULL, "%llu\n");
> +
> +static int ltc4283_show_fet_short_fault_log(void *arg, u64 *val)
> +{
> +	return ltc4283_show_fault_log(arg, val, LTC4283_FET_SHORT_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_fet_short_fault_log,
> +			 ltc4283_show_fet_short_fault_log, NULL, "%llu\n");
> +
> +static int ltc4283_show_curr1_crit_fault_log(void *arg, u64 *val)
> +{
> +	return ltc4283_show_fault_log(arg, val, LTC4283_OC_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_curr1_crit_fault_log,
> +			 ltc4283_show_curr1_crit_fault_log, NULL, "%llu\n");
> +
> +static int ltc4283_show_power1_failed_fault_log(void *arg, u64 *val)
> +{
> +	return ltc4283_show_fault_log(arg, val, LTC4283_PWR_FAIL_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_power1_failed_fault_log,
> +			 ltc4283_show_power1_failed_fault_log, NULL, "%llu\n");
> +
> +static int ltc4283_show_power1_good_input_fault_log(void *arg, u64 *val)
> +{
> +	return ltc4283_show_fault_log(arg, val, LTC4283_PGI_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_power1_good_input_fault_log,
> +			 ltc4283_show_power1_good_input_fault_log, NULL, "%llu\n");
> +
> +static void ltc4283_debugfs_init(struct ltc4283_hwmon *st, struct i2c_client *i2c)
> +{
> +	debugfs_create_file_unsafe("in0_crit_fault_log", 0400, i2c->debugfs, st,
> +				   &ltc4283_in0_crit_fault_log);
> +	debugfs_create_file_unsafe("in0_lcrit_fault_log", 0400, i2c->debugfs, st,
> +				   &ltc4283_in0_lcrit_fault_log);
> +	debugfs_create_file_unsafe("in0_fet_bad_fault_log", 0400, i2c->debugfs, st,
> +				   &ltc4283_fet_bad_fault_log);
> +	debugfs_create_file_unsafe("in0_fet_short_fault_log", 0400, i2c->debugfs, st,
> +				   &ltc4283_fet_short_fault_log);
> +	debugfs_create_file_unsafe("curr1_crit_fault_log", 0400, i2c->debugfs, st,
> +				   &ltc4283_curr1_crit_fault_log);
> +	debugfs_create_file_unsafe("power1_failed_fault_log", 0400, i2c->debugfs, st,
> +				   &ltc4283_power1_failed_fault_log);
> +	debugfs_create_file_unsafe("power1_good_input_fault_log", 0400, i2c->debugfs,
> +				   st, &ltc4283_power1_good_input_fault_log);
> +}
> +
> +static bool ltc4283_is_word_reg(unsigned int reg)
> +{
> +	return reg >= LTC4283_SENSE && reg <= LTC4283_ADIO34_MAX;
> +}
> +
> +static int ltc4283_reg_read(void *context, unsigned int reg, unsigned int *val)
> +{
> +	struct i2c_client *client = context;
> +	int ret;
> +
> +	if (ltc4283_is_word_reg(reg))
> +		ret = i2c_smbus_read_word_swapped(client, reg);
> +	else
> +		ret = i2c_smbus_read_byte_data(client, reg);
> +
> +	if (ret < 0)
> +		return ret;
> +
> +	*val = ret;
> +	return 0;
> +}
> +
> +static int ltc4283_reg_write(void *context, unsigned int reg, unsigned int val)
> +{
> +	struct i2c_client *client = context;
> +
> +	if (ltc4283_is_word_reg(reg))
> +		return i2c_smbus_write_word_swapped(client, reg, val);
> +
> +	return i2c_smbus_write_byte_data(client, reg, val);
> +}
> +
> +static const struct regmap_bus ltc4283_regmap_bus = {
> +	.reg_read = ltc4283_reg_read,
> +	.reg_write = ltc4283_reg_write,
> +};
> +
> +static bool ltc4283_writable_reg(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case LTC4283_SYSTEM_STATUS ... LTC4283_FAULT_STATUS:
> +		return false;
> +	case LTC4283_RESERVED_OC:
> +		return false;
> +	case LTC4283_RESERVED_86 ... LTC4283_RESERVED_8F:
> +		return false;
> +	case LTC4283_RESERVED_91 ... LTC4283_RESERVED_A1:
> +		return false;
> +	case LTC4283_RESERVED_A3:
> +		return false;
> +	case LTC4283_RESERVED_AC:
> +		return false;
> +	case LTC4283_POWER_PLAY_MSB ... LTC4283_POWER_PLAY_LSB:
> +		return false;
> +	case LTC4283_RESERVED_F1 ... LTC4283_RESERVED_FF:
> +		return false;
> +	default:
> +		return true;
> +	}
> +}
> +
> +static const struct regmap_config ltc4283_regmap_config = {
> +	.reg_bits = 8,
> +	.val_bits = 16,
> +	.max_register = 0xFF,
> +	.writeable_reg = ltc4283_writable_reg,
> +};
> +
> +static int ltc4283_probe(struct i2c_client *client)
> +{
> +	struct device *dev = &client->dev, *hwmon;
> +	struct auxiliary_device *adev;
> +	struct ltc4283_hwmon *st;
> +	int ret, id;
> +
> +	st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
> +	if (!st)
> +		return -ENOMEM;
> +
> +	if (!i2c_check_functionality(client->adapter,
> +				     I2C_FUNC_SMBUS_BYTE_DATA |
> +				     I2C_FUNC_SMBUS_WORD_DATA |
> +				     I2C_FUNC_SMBUS_READ_I2C_BLOCK))
> +		return -EOPNOTSUPP;
> +
> +	st->client = client;
> +	st->map = devm_regmap_init(dev, &ltc4283_regmap_bus, client,
> +				   &ltc4283_regmap_config);
> +	if (IS_ERR(st->map))
> +		return dev_err_probe(dev, PTR_ERR(st->map),
> +				     "Failed to create regmap\n");
> +
> +	ret = ltc4283_setup(st, dev);
> +	if (ret)
> +		return ret;
> +
> +	hwmon = devm_hwmon_device_register_with_info(dev, "ltc4283", st,
> +						     &ltc4283_chip_info, NULL);
> +
> +	if (IS_ERR(hwmon))
> +		return PTR_ERR(hwmon);
> +
> +	ltc4283_debugfs_init(st, client);
> +
> +	if (!st->gpio_mask)
> +		return 0;
> +
> +	id = (client->adapter->nr << 10) | client->addr;
> +	adev = __devm_auxiliary_device_create(dev, KBUILD_MODNAME, "gpio",
> +					      NULL, id);
> +	if (!adev)
> +		return dev_err_probe(dev, -ENODEV, "Failed to add GPIO device\n");
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id ltc4283_of_match[] = {
> +	{ .compatible = "adi,ltc4283" },
> +	{ }
> +};
> +
> +static const struct i2c_device_id ltc4283_i2c_id[] = {
> +	{ "ltc4283" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, ltc4283_i2c_id);
> +
> +static struct i2c_driver ltc4283_driver = {
> +	.driver	= {
> +		.name = "ltc4283",
> +		.of_match_table = ltc4283_of_match,
> +	},
> +	.probe = ltc4283_probe,
> +	.id_table = ltc4283_i2c_id,
> +};
> +module_i2c_driver(ltc4283_driver);
> +
> +MODULE_AUTHOR("Nuno Sá <nuno.sa@analog.com>");
> +MODULE_DESCRIPTION("LTC4283 Hot Swap Controller driver");
> +MODULE_LICENSE("GPL");
> 


^ permalink raw reply

* Re: [PATCH V3 6/9] iio: imu: inv_icm42607: Add Accelerometer for icm42607
From: David Lechner @ 2026-04-10 22:59 UTC (permalink / raw)
  To: Chris Morgan, linux-iio
  Cc: andy, nuno.sa, jic23, jean-baptiste.maneyrol, linux-rockchip,
	devicetree, heiko, conor+dt, krzk+dt, robh, andriy.shevchenko,
	Chris Morgan
In-Reply-To: <20260330195853.392877-7-macroalpha82@gmail.com>

On 3/30/26 2:58 PM, Chris Morgan wrote:
> From: Chris Morgan <macromorgan@hotmail.com>
> 
> Add icm42607 accelerometer sensor for icm42607.
> 

...

> +static const unsigned long inv_icm42607_accel_scan_masks[] = {
> +	/* 3-axis accel + temperature */
> +	INV_ICM42607_SCAN_MASK_ACCEL_3AXIS | INV_ICM42607_SCAN_MASK_TEMP,

This is going to make it so that the temperature channel is always read
even if it isn't enabled and additional work is needed when pushing to
buffers to remove it again.

It looks like it is possible to read accel and temp separatly, so
there shuold be two more lines here,

	INV_ICM42607_SCAN_MASK_ACCEL_3AXIS,
	INV_ICM42607_SCAN_MASK_TEMP,

I forget what the correct order is though.

> +	0,
> +};
> +
> +/* enable accelerometer sensor and FIFO write */
> +static int inv_icm42607_accel_update_scan_mode(struct iio_dev *indio_dev,
> +					       const unsigned long *scan_mask)
> +{
> +	struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev);
> +	struct inv_icm42607_sensor_state *accel_st = iio_priv(indio_dev);
> +	struct inv_icm42607_sensor_conf conf = INV_ICM42607_SENSOR_CONF_INIT;
> +	unsigned int fifo_en = 0;
> +	unsigned int sleep_temp = 0;
> +	unsigned int sleep_accel = 0;
> +	unsigned int sleep;
> +	int ret;
> +
> +	mutex_lock(&st->lock);
> +
> +	if (*scan_mask & INV_ICM42607_SCAN_MASK_TEMP) {
> +		/* enable temp sensor */
> +		ret = inv_icm42607_set_temp_conf(st, true, &sleep_temp);
> +		if (ret)
> +			goto out_unlock;
> +		fifo_en |= INV_ICM42607_SENSOR_TEMP;
> +	}
> +
> +	if (*scan_mask & INV_ICM42607_SCAN_MASK_ACCEL_3AXIS) {
> +		/* enable accel sensor */
> +		conf.mode = accel_st->power_mode;
> +		conf.filter = accel_st->filter;
> +		ret = inv_icm42607_set_accel_conf(st, &conf, &sleep_accel);
> +		if (ret)
> +			goto out_unlock;
> +		fifo_en |= INV_ICM42607_SENSOR_ACCEL;
> +	}
> +
> +	/* update data FIFO write */
> +	ret = inv_icm42607_buffer_set_fifo_en(st, fifo_en | st->fifo.en);
> +
> +out_unlock:
> +	mutex_unlock(&st->lock);
> +	/* sleep maximum required time */

Would be better if the comment explain _why_ we need to sleep.

The code is pretty obvious that it does what the comment says, so
it doesn't add much.

> +	sleep = max(sleep_accel, sleep_temp);
> +	if (sleep)

Probably don't need the if here as msleep() should handle 0 without actually
sleeping.

> +		msleep(sleep);
> +	return ret;
> +}
> +
> +static int inv_icm42607_accel_read_sensor(struct iio_dev *indio_dev,
> +					  struct iio_chan_spec const *chan,
> +					  s16 *val)
> +{
> +	struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev);
> +	struct inv_icm42607_sensor_state *accel_st = iio_priv(indio_dev);
> +	struct device *dev = regmap_get_device(st->map);
> +	struct inv_icm42607_sensor_conf conf = INV_ICM42607_SENSOR_CONF_INIT;
> +	unsigned int reg;
> +	__be16 *data;
> +	int ret;
> +
> +	if (chan->type != IIO_ACCEL)
> +		return -EINVAL;
> +
> +	switch (chan->channel2) {
> +	case IIO_MOD_X:
> +		reg = INV_ICM42607_REG_ACCEL_DATA_X1;
> +		break;
> +	case IIO_MOD_Y:
> +		reg = INV_ICM42607_REG_ACCEL_DATA_Y1;
> +		break;
> +	case IIO_MOD_Z:
> +		reg = INV_ICM42607_REG_ACCEL_DATA_Z1;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm);
> +	if (PM_RUNTIME_ACQUIRE_ERR(&pm))
> +		return -ENXIO;
> +
> +	guard(mutex)(&st->lock);
> +
> +	/* enable accel sensor */
> +	conf.mode = accel_st->power_mode;
> +	conf.filter = accel_st->filter;
> +	ret = inv_icm42607_set_accel_conf(st, &conf, NULL);
> +	if (ret)
> +		return ret;
> +
> +	/* read accel register data */
> +	data = (__be16 *)&st->buffer[0];
> +	ret = regmap_bulk_read(st->map, reg, data, sizeof(*data));
> +	if (ret)
> +		return ret;
> +
> +	*val = (int16_t)be16_to_cpup(data);

We don't use int16_t in the kernel (ideally). Stick with s16.

Although cast isn't needed here since val is already s16.

> +	if (*val == INV_ICM42607_DATA_INVALID)
> +		ret = -EINVAL;
> +
> +	return ret;
> +}
> +
> +/* IIO format int + nano */

Usually we make these 2-D arrays for readability and then cast to int * if needed.

> +static const int inv_icm42607_accel_scale[] = {
> +	/* +/- 16G => 0.004788403 m/s-2 */
> +	[2 * INV_ICM42607_ACCEL_FS_16G] = 0,
> +	[2 * INV_ICM42607_ACCEL_FS_16G + 1] = 4788403,
> +	/* +/- 8G => 0.002394202 m/s-2 */
> +	[2 * INV_ICM42607_ACCEL_FS_8G] = 0,
> +	[2 * INV_ICM42607_ACCEL_FS_8G + 1] = 2394202,
> +	/* +/- 4G => 0.001197101 m/s-2 */
> +	[2 * INV_ICM42607_ACCEL_FS_4G] = 0,
> +	[2 * INV_ICM42607_ACCEL_FS_4G + 1] = 1197101,
> +	/* +/- 2G => 0.000598550 m/s-2 */
> +	[2 * INV_ICM42607_ACCEL_FS_2G] = 0,
> +	[2 * INV_ICM42607_ACCEL_FS_2G + 1] = 598550,
> +};
> +

...

> +static int inv_icm42607_accel_read_calibbias(struct inv_icm42607_state *st,
> +					     struct iio_chan_spec const *chan,
> +					     int *val, int *val2)
> +{
> +	/* Not actually supported in the ICM-42607P registers */
> +	return -EOPNOTSUPP;
> +}

Can we just not create the attribute instead of returning an error?


> +static int inv_icm42607_accel_write_raw_get_fmt(struct iio_dev *indio_dev,
> +						struct iio_chan_spec const *chan,
> +						long mask)
> +{
> +	if (chan->type != IIO_ACCEL)
> +		return -EINVAL;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_SCALE:
> +		return IIO_VAL_INT_PLUS_NANO;
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		return IIO_VAL_INT_PLUS_MICRO;
> +	case IIO_CHAN_INFO_CALIBBIAS:
> +		return IIO_VAL_INT_PLUS_MICRO;

Can write this as:

	case IIO_CHAN_INFO_SAMP_FREQ:
	case IIO_CHAN_INFO_CALIBBIAS:
		return IIO_VAL_INT_PLUS_MICRO;

> +	default:
> +		return -EINVAL;
> +	}
> +}
> +

...

> +int inv_icm42607_set_accel_conf(struct inv_icm42607_state *st,
> +				struct inv_icm42607_sensor_conf *conf,
> +				unsigned int *sleep_ms)
> +{
> +	struct inv_icm42607_sensor_conf *oldconf = &st->conf.accel;
> +	unsigned int val;
> +	int ret;
> +
> +	if (conf->mode < 0)
> +		conf->mode = oldconf->mode;
> +	if (conf->fs < 0)
> +		conf->fs = oldconf->fs;
> +	if (conf->odr < 0)
> +		conf->odr = oldconf->odr;
> +	if (conf->filter < 0)
> +		conf->filter = oldconf->filter;
> +
> +	if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) {

We could use the regmap cache feature to avoid having to manual keep
track of old values. Or just always write the same values anyway. I
find that is nice when debugging hardware with a logic analyzer. Unless
there is some measureable performance improvlment here?

> +		val = INV_ICM42607_ACCEL_CONFIG0_FS_SEL(conf->fs) |
> +		INV_ICM42607_ACCEL_CONFIG0_ODR(conf->odr);
> +		ret = regmap_write(st->map, INV_ICM42607_REG_ACCEL_CONFIG0, val);
> +		if (ret)
> +			return ret;
> +		oldconf->fs = conf->fs;
> +		oldconf->odr = conf->odr;
> +	}
> +
> +	if (conf->filter != oldconf->filter) {
> +		if (conf->mode == INV_ICM42607_SENSOR_MODE_LOW_POWER) {
> +			val = INV_ICM42607_ACCEL_CONFIG1_AVG(conf->filter);
> +			ret = regmap_update_bits(st->map, INV_ICM42607_REG_ACCEL_CONFIG1,
> +						 INV_ICM42607_ACCEL_CONFIG1_AVG_MASK, val);
> +		} else {
> +			val = INV_ICM42607_ACCEL_CONFIG1_FILTER(conf->filter);
> +			ret = regmap_update_bits(st->map, INV_ICM42607_REG_ACCEL_CONFIG1,
> +						 INV_ICM42607_ACCEL_CONFIG1_FILTER_MASK, val);
> +		}
> +		if (ret)
> +			return ret;
> +		oldconf->filter = conf->filter;
> +	}
> +
> +	return inv_icm42607_set_pwr_mgmt0(st, st->conf.gyro.mode, conf->mode,
> +					  st->conf.temp_en, sleep_ms);
> +}
> +

^ permalink raw reply

* [PATCH] dt-bindings: thermal: Fix false warning with 'phandle' in trips nodes
From: Rob Herring (Arm) @ 2026-04-10 22:36 UTC (permalink / raw)
  To: Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
	Krzysztof Kozlowski, Conor Dooley
  Cc: linux-pm, devicetree, linux-kernel

A pattern property matching essentially anything doesn't work if there
are implicit properties such as 'phandle' which can occur on any node.
One such example popped up recently:

arch/arm64/boot/dts/qcom/sm8650-hdk.dtb: thermal-zones: gpuss0-thermal:trips:phandle: 531 is not of type 'object'
        from schema $id: http://devicetree.org/schemas/thermal/thermal-zones.yaml

Instead of a pattern property, use an "additionalProperties" schema
instead which is the fallback in case of no matching property.

Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
Daniel, Please pick this up for v7.1 as the above warning is in next. Or 
if you prefer, I can take it.

 .../bindings/thermal/thermal-zones.yaml       | 111 +++++++++---------
 1 file changed, 54 insertions(+), 57 deletions(-)

diff --git a/Documentation/devicetree/bindings/thermal/thermal-zones.yaml b/Documentation/devicetree/bindings/thermal/thermal-zones.yaml
index 0de0a9757ccc..07d9f576ffe7 100644
--- a/Documentation/devicetree/bindings/thermal/thermal-zones.yaml
+++ b/Documentation/devicetree/bindings/thermal/thermal-zones.yaml
@@ -129,63 +129,60 @@ patternProperties:
           which the thermal framework needs to take action. The actions to
           be taken are defined in another node called cooling-maps.
 
-        patternProperties:
-          "^[a-zA-Z][a-zA-Z0-9\\-_]{0,63}$":
-            type: object
-
-            properties:
-              temperature:
-                $ref: /schemas/types.yaml#/definitions/int32
-                minimum: -273000
-                maximum: 200000
-                description:
-                  An integer expressing the trip temperature in millicelsius.
-
-              hysteresis:
-                $ref: /schemas/types.yaml#/definitions/uint32
-                description:
-                  An unsigned integer expressing the hysteresis delta with
-                  respect to the trip temperature property above, also in
-                  millicelsius. Any cooling action initiated by the framework is
-                  maintained until the temperature falls below
-                  (trip temperature - hysteresis). This potentially prevents a
-                  situation where the trip gets constantly triggered soon after
-                  cooling action is removed.
-
-              type:
-                $ref: /schemas/types.yaml#/definitions/string
-                enum:
-                  - active   # enable active cooling e.g. fans
-                  - passive  # enable passive cooling e.g. throttling cpu
-                  - hot      # send notification to driver
-                  - critical # send notification to driver, trigger shutdown
-                description: |
-                  There are four valid trip types: active, passive, hot,
-                  critical.
-
-                  The critical trip type is used to set the maximum
-                  temperature threshold above which the HW becomes
-                  unstable and underlying firmware might even trigger a
-                  reboot. Hitting the critical threshold triggers a system
-                  shutdown.
-
-                  The hot trip type can be used to send a notification to
-                  the thermal driver (if a .notify callback is registered).
-                  The action to be taken is left to the driver.
-
-                  The passive trip type can be used to slow down HW e.g. run
-                  the CPU, GPU, bus at a lower frequency.
-
-                  The active trip type can be used to control other HW to
-                  help in cooling e.g. fans can be sped up or slowed down
-
-            required:
-              - temperature
-              - hysteresis
-              - type
-            additionalProperties: false
-
-        additionalProperties: false
+        additionalProperties:
+          type: object
+          additionalProperties: false
+
+          properties:
+            temperature:
+              $ref: /schemas/types.yaml#/definitions/int32
+              minimum: -273000
+              maximum: 200000
+              description:
+                An integer expressing the trip temperature in millicelsius.
+
+            hysteresis:
+              $ref: /schemas/types.yaml#/definitions/uint32
+              description:
+                An unsigned integer expressing the hysteresis delta with
+                respect to the trip temperature property above, also in
+                millicelsius. Any cooling action initiated by the framework is
+                maintained until the temperature falls below
+                (trip temperature - hysteresis). This potentially prevents a
+                situation where the trip gets constantly triggered soon after
+                cooling action is removed.
+
+            type:
+              $ref: /schemas/types.yaml#/definitions/string
+              enum:
+                - active   # enable active cooling e.g. fans
+                - passive  # enable passive cooling e.g. throttling cpu
+                - hot      # send notification to driver
+                - critical # send notification to driver, trigger shutdown
+              description: |
+                There are four valid trip types: active, passive, hot,
+                critical.
+
+                The critical trip type is used to set the maximum
+                temperature threshold above which the HW becomes
+                unstable and underlying firmware might even trigger a
+                reboot. Hitting the critical threshold triggers a system
+                shutdown.
+
+                The hot trip type can be used to send a notification to
+                the thermal driver (if a .notify callback is registered).
+                The action to be taken is left to the driver.
+
+                The passive trip type can be used to slow down HW e.g. run
+                the CPU, GPU, bus at a lower frequency.
+
+                The active trip type can be used to control other HW to
+                help in cooling e.g. fans can be sped up or slowed down
+
+          required:
+            - temperature
+            - hysteresis
+            - type
 
       cooling-maps:
         type: object
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH V3 5/9] iio: imu: inv_icm42607: Add Temperature Support in icm42607
From: David Lechner @ 2026-04-10 22:34 UTC (permalink / raw)
  To: Chris Morgan, linux-iio
  Cc: andy, nuno.sa, jic23, jean-baptiste.maneyrol, linux-rockchip,
	devicetree, heiko, conor+dt, krzk+dt, robh, andriy.shevchenko,
	Chris Morgan
In-Reply-To: <20260330195853.392877-6-macroalpha82@gmail.com>

On 3/30/26 2:58 PM, Chris Morgan wrote:
> From: Chris Morgan <macromorgan@hotmail.com>
> 
> Add functions for reading temperature sensor data.
> 
> Signed-off-by: Chris Morgan <macromorgan@hotmail.com>
> ---
>  drivers/iio/imu/inv_icm42607/inv_icm42607.h   |  3 +
>  .../iio/imu/inv_icm42607/inv_icm42607_core.c  | 17 ++++
>  .../iio/imu/inv_icm42607/inv_icm42607_temp.c  | 81 +++++++++++++++++++
>  .../iio/imu/inv_icm42607/inv_icm42607_temp.h  | 30 +++++++
>  4 files changed, 131 insertions(+)
>  create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_temp.c
>  create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_temp.h
> 
> diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607.h b/drivers/iio/imu/inv_icm42607/inv_icm42607.h
> index 5530fd3bc03f..086848c8fd3b 100644
> --- a/drivers/iio/imu/inv_icm42607/inv_icm42607.h
> +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607.h
> @@ -433,6 +433,9 @@ extern const struct dev_pm_ops inv_icm42607_pm_ops;
>  
>  u32 inv_icm42607_odr_to_period(enum inv_icm42607_odr odr);
>  
> +int inv_icm42607_set_temp_conf(struct inv_icm42607_state *st, bool enable,
> +			       unsigned int *sleep_ms);
> +
>  int inv_icm42607_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg,
>  			     unsigned int writeval, unsigned int *readval);
>  
> diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c
> index 344071089042..735a262dc103 100644
> --- a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c
> +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c
> @@ -164,6 +164,23 @@ static int inv_icm42607_set_pwr_mgmt0(struct inv_icm42607_state *st,
>  	return 0;
>  }
>  
> +int inv_icm42607_set_temp_conf(struct inv_icm42607_state *st, bool enable,
> +			       unsigned int *sleep_ms)
> +{
> +	unsigned int val;
> +	int ret;
> +
> +	val = INV_ICM42607_TEMP_CONFIG0_FILTER(INV_ICM42607_FILTER_BW_34HZ);
> +	ret = regmap_update_bits(st->map, INV_ICM42607_REG_TEMP_CONFIG0,
> +				 INV_ICM42607_TEMP_CONFIG0_FILTER_MASK, val);
> +	if (ret)
> +		return ret;
> +
> +	return inv_icm42607_set_pwr_mgmt0(st, st->conf.gyro.mode,
> +					  st->conf.accel.mode, enable,
> +					  sleep_ms);
> +}
> +
>  int inv_icm42607_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg,
>  			     unsigned int writeval, unsigned int *readval)
>  {
> diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_temp.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_temp.c
> new file mode 100644
> index 000000000000..b42eb78cd960
> --- /dev/null
> +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_temp.c
> @@ -0,0 +1,81 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2026 InvenSense, Inc.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/device.h>
> +#include <linux/mutex.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <linux/iio/iio.h>
> +
> +#include "inv_icm42607.h"
> +#include "inv_icm42607_temp.h"
> +
> +static int inv_icm42607_temp_read(struct inv_icm42607_state *st, s16 *temp)
> +{
> +	struct device *dev = regmap_get_device(st->map);
> +	__be16 *raw;
> +	int ret;
> +
> +	PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm);
> +	if (PM_RUNTIME_ACQUIRE_ERR(&pm))
> +		return -ENXIO;
> +
> +	guard(mutex)(&st->lock);
> +
> +	ret = inv_icm42607_set_temp_conf(st, true, NULL);
> +	if (ret)
> +		return ret;
> +
> +	raw = (__be16 *)&st->buffer[0];

Can we make buffer __be16 buffer[] to avoid cast and ensure proper alignment?

> +	ret = regmap_bulk_read(st->map, INV_ICM42607_REG_TEMP_DATA1, raw, sizeof(*raw));
> +	if (ret)
> +		return ret;
> +
> +	*temp = (s16)be16_to_cpup(raw);

cast is not needed. temp is already s16.

> +	if (*temp == INV_ICM42607_DATA_INVALID)
> +		ret = -EINVAL;
> +
> +	return ret;
> +}
> +
> +int inv_icm42607_temp_read_raw(struct iio_dev *indio_dev,
> +				struct iio_chan_spec const *chan,
> +				int *val, int *val2, long mask)
> +{
> +	struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev);
> +	s16 temp;
> +	int ret;
> +
> +	if (chan->type != IIO_TEMP)
> +		return -EINVAL;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +		if (!iio_device_claim_direct(indio_dev))
> +			return -EBUSY;
> +		ret = inv_icm42607_temp_read(st, &temp);
> +		iio_device_release_direct(indio_dev);
> +		if (ret)
> +			return ret;
> +		*val = temp;
> +		return IIO_VAL_INT;
> +	/*
> +	 * T°C = (temp / 128) + 25
> +	 * Tm°C = 1000 * ((temp * 100 / 12800) + 25)
> +	 * scale: 100000 / 12800 ~= 7.8125
> +	 * offset: 25000
> +	 */
> +	case IIO_CHAN_INFO_SCALE:
> +		*val = 7;
> +		*val2 = 812500;
> +		return IIO_VAL_INT_PLUS_MICRO;

Could use IIO_VAL_INT_PLUS_NANO to get more exact value.

> +	case IIO_CHAN_INFO_OFFSET:
> +		*val = 25000;
> +		return IIO_VAL_INT;
> +	default:
> +		return -EINVAL;
> +	}
> +}

^ permalink raw reply

* Re: [PATCH 2/3] pmdomain: core: add support for power-domains-child-ids
From: Kevin Hilman @ 2026-04-10 22:25 UTC (permalink / raw)
  To: Ulf Hansson
  Cc: Rob Herring, Geert Uytterhoeven, linux-pm, devicetree,
	linux-kernel, arm-scmi, linux-arm-kernel
In-Reply-To: <CAPDyKFrR2zyMFXTAkKs1XRgB-u5jSP256g730s=7SLuOZKsKVg@mail.gmail.com>

Ulf Hansson <ulf.hansson@linaro.org> writes:

> On Fri, 10 Apr 2026 at 02:45, Kevin Hilman <khilman@baylibre.com> wrote:
>>
>> Ulf Hansson <ulf.hansson@linaro.org> writes:
>>
>> > On Wed, 11 Mar 2026 at 01:19, Kevin Hilman (TI) <khilman@baylibre.com> wrote:
>> >>
>> >> Currently, PM domains can only support hierarchy for simple
>> >> providers (e.g. ones with #power-domain-cells = 0).
>> >>
>> >> Add support for oncell providers as well by adding a new property
>> >> `power-domains-child-ids` to describe the parent/child relationship.
>> >>
>> >> For example, an SCMI PM domain provider has multiple domains, each of
>> >> which might be a child of diffeent parent domains. In this example,
>> >> the parent domains are MAIN_PD and WKUP_PD:
>> >>
>> >>     scmi_pds: protocol@11 {
>> >>         reg = <0x11>;
>> >>         #power-domain-cells = <1>;
>> >>         power-domains = <&MAIN_PD>, <&WKUP_PD>;
>> >>         power-domains-child-ids = <15>, <19>;
>> >>     };
>> >>
>> >> With this example using the new property, SCMI PM domain 15 becomes a
>> >> child domain of MAIN_PD, and SCMI domain 19 becomes a child domain of
>> >> WKUP_PD.
>> >>
>> >> To support this feature, add two new core functions
>> >>
>> >> - of_genpd_add_child_ids()
>> >> - of_genpd_remove_child_ids()
>> >>
>> >> which can be called by pmdomain providers to add/remove child domains
>> >> if they support the new property power-domains-child-ids.
>> >>
>> >> Signed-off-by: Kevin Hilman (TI) <khilman@baylibre.com>
>> >
>> > Thanks for working on this! It certainly is a missing feature!
>>
>> You're welcome, thanks for the detailed review.
>>
>> >> ---
>> >>  drivers/pmdomain/core.c   | 169 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>> >>  include/linux/pm_domain.h |  16 ++++++++++++++++
>> >>  2 files changed, 185 insertions(+)
>> >>
>> >> diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c
>> >> index 61c2277c9ce3..acb45dd540b7 100644
>> >> --- a/drivers/pmdomain/core.c
>> >> +++ b/drivers/pmdomain/core.c
>> >> @@ -2909,6 +2909,175 @@ static struct generic_pm_domain *genpd_get_from_provider(
>> >>         return genpd;
>> >>  }
>> >>
>> >> +/**
>> >> + * of_genpd_add_child_ids() - Parse power-domains-child-ids property
>> >> + * @np: Device node pointer associated with the PM domain provider.
>> >> + * @data: Pointer to the onecell data associated with the PM domain provider.
>> >> + *
>> >> + * Parse the power-domains and power-domains-child-ids properties to establish
>> >> + * parent-child relationships for PM domains. The power-domains property lists
>> >> + * parent domains, and power-domains-child-ids lists which child domain IDs
>> >> + * should be associated with each parent.
>> >> + *
>> >> + * Returns 0 on success, -ENOENT if properties don't exist, or negative error code.
>> >
>> > I think we should avoid returning specific error codes for specific
>> > errors, simply because it usually becomes messy.
>> >
>> > If I understand correctly the intent here is to allow the caller to
>> > check for -ENOENT and potentially avoid bailing out as it may not
>> > really be an error, right?
>>
>> Right, -ENOENT is not an error of parsing, it's to indicate that there
>> are no child-ids to be parsed.
>>
>> > Perhaps a better option is to return the number of children for whom
>> > we successfully assigned parents. Hence 0 or a positive value allows
>> > the caller to understand what happened. More importantly, a negative
>> > error code then really becomes an error for the caller to consider.
>>
>> I explored this a bit, but it gets messy quick.  It means we have to
>> track cases where only some of the children were added as well as when
>> all children were added.   Personally, I think this should be an "all or
>> nothing" thing.  If all the children cannot be parsed/added, then none
>> of them should be added.
>>
>> This also allows the remove to not have to care about how many were
>> added, and just remove them all, with the additional benefit of not
>> having to track the state of how many children were successfully added.
>>
>
> I fully agree, it should be all or nothing. Failing with one
> child/parent should end up with an error code being returned.
>
> That said, it still seems to make perfect sense to return the number
> of children for whom we assigned parents for, no?

No, because what will the caller use that number for?  If we are
assuming "all or nothing", what would we use it for (other than a debug print?)

It also makes it a bit confusing what a zero return value means.  Does
that mean success?  Or that zero children were added (which would be
fail.)

I prefer to keep it as is.

Kevin

^ permalink raw reply

* Re: [PATCH V3 3/9] iio: imu: inv_icm42607: Add I2C and SPI For icm42607
From: David Lechner @ 2026-04-10 22:21 UTC (permalink / raw)
  To: Chris Morgan, linux-iio
  Cc: andy, nuno.sa, jic23, jean-baptiste.maneyrol, linux-rockchip,
	devicetree, heiko, conor+dt, krzk+dt, robh, andriy.shevchenko,
	Chris Morgan
In-Reply-To: <20260330195853.392877-4-macroalpha82@gmail.com>

On 3/30/26 2:58 PM, Chris Morgan wrote:
> From: Chris Morgan <macromorgan@hotmail.com>
> 
> Add I2C and SPI driver support for InvenSense ICM-42607 devices.

> Include runtime power management on each device.

Power management seems unrelated, so likey belongs in a separate patch.

> 
> Signed-off-by: Chris Morgan <macromorgan@hotmail.com>
> ---
>  drivers/iio/imu/inv_icm42607/inv_icm42607.h   |  14 ++
>  .../iio/imu/inv_icm42607/inv_icm42607_core.c  | 204 ++++++++++++++++++
>  .../iio/imu/inv_icm42607/inv_icm42607_i2c.c   |  93 ++++++++
>  .../iio/imu/inv_icm42607/inv_icm42607_spi.c   | 100 +++++++++
>  4 files changed, 411 insertions(+)
>  create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_i2c.c
>  create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c
> 
> diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607.h b/drivers/iio/imu/inv_icm42607/inv_icm42607.h
> index 609188c40ffc..7d13091aa8df 100644
> --- a/drivers/iio/imu/inv_icm42607/inv_icm42607.h
> +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607.h
> @@ -11,6 +11,7 @@
>  #include <linux/regmap.h>
>  #include <linux/mutex.h>
>  #include <linux/regulator/consumer.h>
> +#include <linux/pm.h>
>  #include <linux/iio/iio.h>
>  #include <linux/iio/common/inv_sensors_timestamp.h>
>  
> @@ -21,6 +22,16 @@ enum inv_icm42607_chip {
>  	INV_CHIP_NB,
>  };
>  
> +/* serial bus slew rates */
> +enum inv_icm42607_slew_rate {
> +	INV_ICM42607_SLEW_RATE_20_60NS,
> +	INV_ICM42607_SLEW_RATE_12_36NS,
> +	INV_ICM42607_SLEW_RATE_6_18NS,
> +	INV_ICM42607_SLEW_RATE_4_12NS,
> +	INV_ICM42607_SLEW_RATE_2_6NS,
> +	INV_ICM42607_SLEW_RATE_INF_2NS,
> +};
> +
>  enum inv_icm42607_sensor_mode {
>  	INV_ICM42607_SENSOR_MODE_OFF,
>  	INV_ICM42607_SENSOR_MODE_STANDBY,
> @@ -413,6 +424,9 @@ struct inv_icm42607_sensor_state {
>  
>  typedef int (*inv_icm42607_bus_setup)(struct inv_icm42607_state *);
>  
> +extern const struct regmap_config inv_icm42607_regmap_config;
> +extern const struct dev_pm_ops inv_icm42607_pm_ops;
> +
>  u32 inv_icm42607_odr_to_period(enum inv_icm42607_odr odr);
>  
>  int inv_icm42607_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg,
> diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c
> index 6b7078387568..da04c820dab2 100644
> --- a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c
> +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c
> @@ -12,12 +12,33 @@
>  #include <linux/interrupt.h>
>  #include <linux/irq.h>
>  #include <linux/regulator/consumer.h>
> +#include <linux/pm_runtime.h>
>  #include <linux/property.h>
>  #include <linux/regmap.h>
>  #include <linux/iio/iio.h>
>  
>  #include "inv_icm42607.h"
>  
> +static const struct regmap_range_cfg inv_icm42607_regmap_ranges[] = {
> +	{
> +		.name = "user bank",
> +		.range_min = 0x0000,
> +		.range_max = 0x00FF,
> +		.window_start = 0,
> +		.window_len = 0x0100,
> +	},
> +};
> +
> +const struct regmap_config inv_icm42607_regmap_config = {
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.max_register = 0x00FF,
> +	.ranges = inv_icm42607_regmap_ranges,
> +	.num_ranges = ARRAY_SIZE(inv_icm42607_regmap_ranges),
> +	.cache_type = REGCACHE_NONE,
> +};
> +EXPORT_SYMBOL_NS_GPL(inv_icm42607_regmap_config, "IIO_ICM42607");

It would make more sense to include the regmap config in the first patch
since it is shared.

> +
>  struct inv_icm42607_hw {
>  	uint8_t whoami;
>  	const char *name;
> @@ -86,6 +107,62 @@ u32 inv_icm42607_odr_to_period(enum inv_icm42607_odr odr)
>  	return odr_periods[odr];
>  }
>  
> +static int inv_icm42607_set_pwr_mgmt0(struct inv_icm42607_state *st,
> +				      enum inv_icm42607_sensor_mode gyro,
> +				      enum inv_icm42607_sensor_mode accel,
> +				      bool temp, unsigned int *sleep_ms)
> +{
> +	enum inv_icm42607_sensor_mode oldgyro = st->conf.gyro.mode;
> +	enum inv_icm42607_sensor_mode oldaccel = st->conf.accel.mode;
> +	bool oldtemp = st->conf.temp_en;
> +	unsigned int sleepval;
> +	unsigned int val;
> +	int ret;
> +
> +	if (gyro == oldgyro && accel == oldaccel && temp == oldtemp)
> +		return 0;
> +
> +	val = INV_ICM42607_PWR_MGMT0_GYRO(gyro) |
> +	INV_ICM42607_PWR_MGMT0_ACCEL(accel);
> +	if (!temp)
> +		val |= INV_ICM42607_PWR_MGMT0_ACCEL_LP_CLK_SEL;
> +	ret = regmap_write(st->map, INV_ICM42607_REG_PWR_MGMT0, val);
> +	if (ret)
> +		return ret;
> +
> +	st->conf.gyro.mode = gyro;
> +	st->conf.accel.mode = accel;
> +	st->conf.temp_en = temp;
> +
> +	sleepval = 0;
> +	if (temp && !oldtemp) {
> +		if (sleepval < INV_ICM42607_TEMP_STARTUP_TIME_MS)
> +			sleepval = INV_ICM42607_TEMP_STARTUP_TIME_MS;
> +	}
> +	if (accel != oldaccel && oldaccel == INV_ICM42607_SENSOR_MODE_OFF) {
> +		usleep_range(200, 300);
> +		if (sleepval < INV_ICM42607_ACCEL_STARTUP_TIME_MS)
> +			sleepval = INV_ICM42607_ACCEL_STARTUP_TIME_MS;
> +	}
> +	if (gyro != oldgyro) {
> +		if (oldgyro == INV_ICM42607_SENSOR_MODE_OFF) {
> +			usleep_range(200, 300);
> +			if (sleepval < INV_ICM42607_GYRO_STARTUP_TIME_MS)
> +				sleepval = INV_ICM42607_GYRO_STARTUP_TIME_MS;
> +		} else if (gyro == INV_ICM42607_SENSOR_MODE_OFF) {
> +			if (sleepval < INV_ICM42607_GYRO_STOP_TIME_MS)
> +				sleepval = INV_ICM42607_GYRO_STOP_TIME_MS;
> +		}
> +	}
> +
> +	if (sleep_ms)
> +		*sleep_ms = sleepval;
> +	else if (sleepval)
> +		msleep(sleepval);
> +
> +	return 0;
> +}
> +
>  int inv_icm42607_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg,
>  			     unsigned int writeval, unsigned int *readval)
>  {
> @@ -219,6 +296,10 @@ static int inv_icm42607_enable_vddio_reg(struct inv_icm42607_state *st)
>  static void inv_icm42607_disable_vddio_reg(void *_data)
>  {
>  	struct inv_icm42607_state *st = _data;
> +	struct device *dev = regmap_get_device(st->map);
> +
> +	if (pm_runtime_status_suspended(dev))
> +		return;
>  
>  	regulator_disable(st->vddio_supply);
>  }
> @@ -289,11 +370,134 @@ int inv_icm42607_core_probe(struct regmap *regmap, int chip,
>  
>  	/* Setup chip registers (includes WHOAMI check, reset check, bus setup) */
>  	ret = inv_icm42607_setup(st, bus_setup);
> +	if (ret)
> +		return ret; /* Return error from setup (e.g., WHOAMI fail) */
> +
> +	/* Setup runtime power management */

Would add a blank line here if this is meant to apply to more than the
following line. Otherwise it doesn't add much.

> +	ret = devm_pm_runtime_set_active_enabled(dev);
> +	if (ret)
> +		return ret;
> +
> +	pm_runtime_set_autosuspend_delay(dev, INV_ICM42607_SUSPEND_DELAY_MS);
> +	pm_runtime_use_autosuspend(dev);
>  
>  	return ret;
>  }
>  EXPORT_SYMBOL_NS_GPL(inv_icm42607_core_probe, "IIO_ICM42607");
>  

...

> diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_i2c.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_i2c.c
> new file mode 100644
> index 000000000000..eb72973debc5
> --- /dev/null
> +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_i2c.c
> @@ -0,0 +1,93 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2026 InvenSense, Inc.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/i2c.h>
> +#include <linux/regmap.h>
> +#include <linux/property.h>
> +
> +#include "inv_icm42607.h"
> +
> +static int inv_icm42607_i2c_bus_setup(struct inv_icm42607_state *st)
> +{
> +	unsigned int mask, val;
> +	int ret;
> +
> +	ret = regmap_update_bits(st->map, INV_ICM42607_REG_INTF_CONFIG1,
> +				 INV_ICM42607_INTF_CONFIG1_I3C_DDR_EN |
> +				 INV_ICM42607_INTF_CONFIG1_I3C_SDR_EN, 0);

regmap_clear_bits()

> +	if (ret)
> +		return ret;
> +
> +	mask = INV_ICM42607_DRIVE_CONFIG2_I2C_MASK;

Local mask variable isn't helping much.

> +	val = INV_ICM42607_DRIVE_CONFIG2_I2C(INV_ICM42607_SLEW_RATE_12_36NS);
> +	ret = regmap_update_bits(st->map, INV_ICM42607_REG_DRIVE_CONFIG2,
> +				 mask, val);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_update_bits(st->map, INV_ICM42607_REG_INTF_CONFIG0,
> +				  INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_MASK,
> +				  INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_SPI_DIS);
> +}
> +
> +static int inv_icm42607_probe(struct i2c_client *client)
> +{
> +	const void *match;
> +	enum inv_icm42607_chip chip;
> +	struct regmap *regmap;
> +
> +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK))
> +		return -EOPNOTSUPP;
> +
> +	match = device_get_match_data(&client->dev);

Should be i2c_get_match_data(). And we recently decided to standardize
on not checking for NULL return.

> +	if (!match)
> +		return -EINVAL;
> +	chip = (uintptr_t)match;
> +
> +	regmap = devm_regmap_init_i2c(client, &inv_icm42607_regmap_config);
> +	if (IS_ERR(regmap))
> +		return PTR_ERR(regmap);
> +
> +	return inv_icm42607_core_probe(regmap, chip, inv_icm42607_i2c_bus_setup);
> +}
> +
> +static const struct i2c_device_id inv_icm42607_id[] = {
> +	{ "icm42607", INV_CHIP_ICM42607 },
> +	{ "icm42607p", INV_CHIP_ICM42607P },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, inv_icm42607_id);
> +
> +static const struct of_device_id inv_icm42607_of_matches[] = {
> +	{
> +		.compatible = "invensense,icm42607",
> +		.data = (void *)INV_CHIP_ICM42607,
> +	}, {
> +		.compatible = "invensense,icm42607p",
> +		.data = (void *)INV_CHIP_ICM42607P,
> +	},
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, inv_icm42607_of_matches);
> +
> +static struct i2c_driver inv_icm42607_driver = {
> +	.driver = {
> +		.name = "inv-icm42607-i2c",
> +		.of_match_table = inv_icm42607_of_matches,
> +		.pm = pm_ptr(&inv_icm42607_pm_ops),
> +	},
> +	.id_table = inv_icm42607_id,
> +	.probe = inv_icm42607_probe,
> +};
> +module_i2c_driver(inv_icm42607_driver);
> +
> +MODULE_AUTHOR("InvenSense, Inc.");
> +MODULE_DESCRIPTION("InvenSense ICM-42607x I2C driver");
> +MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_ICM42607");
> diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c
> new file mode 100644
> index 000000000000..51ce3deeb706
> --- /dev/null
> +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c
> @@ -0,0 +1,100 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2026 InvenSense, Inc.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/spi/spi.h>
> +#include <linux/regmap.h>
> +#include <linux/property.h>
> +
> +#include "inv_icm42607.h"
> +
> +static int inv_icm42607_spi_bus_setup(struct inv_icm42607_state *st)
> +{
> +	unsigned int mask, val;
> +	int ret;
> +
> +	ret = regmap_update_bits(st->map, INV_ICM42607_REG_DEVICE_CONFIG,
> +				 INV_ICM42607_DEVICE_CONFIG_SPI_AP_4WIRE,
> +				 INV_ICM42607_DEVICE_CONFIG_SPI_AP_4WIRE);

regmap_set_bits()

> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_update_bits(st->map, INV_ICM42607_REG_INTF_CONFIG1,
> +				 INV_ICM42607_INTF_CONFIG1_I3C_DDR_EN |
> +				 INV_ICM42607_INTF_CONFIG1_I3C_SDR_EN, 0);

regmap_clear_bits()

> +	if (ret)
> +		return ret;
> +
> +	mask = INV_ICM42607_DRIVE_CONFIG3_SPI_MASK;
> +	val = INV_ICM42607_DRIVE_CONFIG3_SPI(INV_ICM42607_SLEW_RATE_INF_2NS);
> +	ret = regmap_update_bits(st->map, INV_ICM42607_REG_DRIVE_CONFIG3,
> +				 mask, val);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_update_bits(st->map, INV_ICM42607_REG_INTF_CONFIG0,
> +				  INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_MASK,
> +				  INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_I2C_DIS);
> +}
> +
> +static int inv_icm42607_probe(struct spi_device *spi)
> +{
> +	const void *match;
> +	enum inv_icm42607_chip chip;
> +	struct regmap *regmap;
> +
> +	match = device_get_match_data(&spi->dev);
> +	if (!match)
> +		return -EINVAL;

Should be spi_get_device_match_data(). And we recently decided to standardize
on not checking for NULL return.

> +	chip = (uintptr_t)match;

uintptr_t is frowned upon in the kernel. Stick with kernel_ulong_t.

> +
> +	regmap = devm_regmap_init_spi(spi, &inv_icm42607_regmap_config);
> +	if (IS_ERR(regmap))
> +		return dev_err_probe(&spi->dev, PTR_ERR(regmap),
> +				     "Failed to register spi regmap %ld\n",
> +				     PTR_ERR(regmap));
> +
> +	return inv_icm42607_core_probe(regmap, chip,
> +				       inv_icm42607_spi_bus_setup);
> +}
> +
> +static const struct of_device_id inv_icm42607_of_matches[] = {
> +	{
> +		.compatible = "invensense,icm42607",
> +		.data = (void *)INV_CHIP_ICM42607,
> +	},
> +	{
> +		.compatible = "invensense,icm42607p",
> +		.data = (void *)INV_CHIP_ICM42607P,
> +	},
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, inv_icm42607_of_matches);
> +
> +static const struct spi_device_id inv_icm42607_spi_id_table[] = {
> +	{ "icm42607", INV_CHIP_ICM42607 },
> +	{ "icm42607p", INV_CHIP_ICM42607P },
> +	{ },

No trailing comma.

> +};
> +MODULE_DEVICE_TABLE(spi, inv_icm42607_spi_id_table);
> +
> +static struct spi_driver inv_icm42607_driver = {
> +	.driver = {
> +		.name = "inv-icm42607-spi",
> +		.of_match_table = inv_icm42607_of_matches,
> +		.pm = &inv_icm42607_pm_ops,
> +	},
> +	.id_table = inv_icm42607_spi_id_table,
> +	.probe = inv_icm42607_probe,
> +};
> +module_spi_driver(inv_icm42607_driver);
> +
> +MODULE_AUTHOR("InvenSense, Inc.");
> +MODULE_DESCRIPTION("InvenSense ICM-42607x SPI driver");
> +MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_ICM42607");


^ permalink raw reply

* Re: [GIT PULL] RISC-V SpacemiT Devicetrees for v7.1
From: Linus Walleij @ 2026-04-10 22:19 UTC (permalink / raw)
  To: Yixun Lan
  Cc: soc, Yixun Lan, Arnd Bergmann, spacemit, linux-riscv, devicetree,
	linux-kernel
In-Reply-To: <20260403123040-KYC0145825@kernel.org>

Hi Yixun,

I looked into this pull request.

I'm sorry if I do stupid mistakes in handling it, I'm new to maintaining
the SoC tree. Bear with me.

On Fri, Apr 3, 2026 at 2:32 PM Yixun Lan <dlan@kernel.org> wrote:

> Aurelien Jarno (7):
>       riscv: dts: spacemit: drop incorrect pinctrl for combo PHY
(...)
> Yixun Lan (9):
>       riscv: dts: spacemit: pcie: fix missing power regulator

[Fixes]
fatal: Not a valid object name linus/master
Commit: c68360c0d636 ("riscv: dts: spacemit: drop incorrect pinctrl
for combo PHY")
    Fixes tag: Fixes: 0be016a4b5d1b9 ("riscv: dts: spacemit: PCIe and
PHY-related updates")
    Has these problem(s):
        - Inspect: Target SHA is not ancestor of Linus' master branch,
which means it is fixing commit in your branch
fatal: Not a valid object name linus/master
Commit: 8a9071299dec ("riscv: dts: spacemit: pcie: fix missing power regulator")
    Fixes tag: Fixes: 0be016a4b5d1 ("riscv: dts: spacemit: PCIe and
PHY-related updates")
    Has these problem(s):
        - Inspect: Target SHA is not ancestor of Linus' master branch,
which means it is fixing commit in your branch

So this means you introduced bugs and fix them in the same pull request?

Why?

The practice is to squash such fixes into the offending patches when
presenting pull requests. But I went ahead anyway, trying to not be so
picky. (The commits are there, in your branch indeed.)

- Checked that it was in linux-next OK
- built DTBS OK

Pulled in, thanks.

Yours,
Linus Walleij

^ permalink raw reply


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