From 9e51d1da5b245c9bf97fc49b06cca7e901c0fe94 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 21 Nov 2025 16:34:40 +0300 Subject: [PATCH 001/297] firmware: stratix10-svc: Delete some stray tabs These lines are indented one tab too far. Delete the extra tabs for readability. Signed-off-by: Dan Carpenter Signed-off-by: Dinh Nguyen --- drivers/firmware/stratix10-svc.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c index 515b948ff320..dbed404a71fc 100644 --- a/drivers/firmware/stratix10-svc.c +++ b/drivers/firmware/stratix10-svc.c @@ -1317,7 +1317,7 @@ int stratix10_svc_async_send(struct stratix10_svc_chan *chan, void *msg, dev_dbg(ctrl->dev, "Async message sent with transaction_id 0x%02x\n", handle->transaction_id); - *handler = handle; + *handler = handle; return 0; case INTEL_SIP_SMC_STATUS_BUSY: dev_warn(ctrl->dev, "Mailbox is busy, try after some time\n"); @@ -1702,12 +1702,12 @@ int stratix10_svc_send(struct stratix10_svc_chan *chan, void *msg) kthread_run_on_cpu(svc_normal_to_secure_thread, (void *)chan->ctrl, cpu, "svc_smc_hvc_thread"); - if (IS_ERR(chan->ctrl->task)) { - dev_err(chan->ctrl->dev, - "failed to create svc_smc_hvc_thread\n"); - kfree(p_data); - return -EINVAL; - } + if (IS_ERR(chan->ctrl->task)) { + dev_err(chan->ctrl->dev, + "failed to create svc_smc_hvc_thread\n"); + kfree(p_data); + return -EINVAL; + } } pr_debug("%s: sent P-va=%p, P-com=%x, P-size=%u\n", __func__, From 7009646d937f9a1147b401362260939bba52082f Mon Sep 17 00:00:00 2001 From: Jie Gan Date: Tue, 2 Sep 2025 12:21:43 +0800 Subject: [PATCH 002/297] dt-binding: Update oss email address for Coresight documents Update the OSS email addresses across all Coresight documents, as the previous addresses have been deprecated. Signed-off-by: Jie Gan Acked-by: Rob Herring (Arm) Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20250902042143.1010-1-jie.gan@oss.qualcomm.com --- .../sysfs-bus-coresight-devices-dummy-source | 4 +- .../testing/sysfs-bus-coresight-devices-tpdm | 56 +++++++++---------- .../arm/arm,coresight-dummy-sink.yaml | 2 +- .../arm/arm,coresight-dummy-source.yaml | 2 +- .../bindings/arm/qcom,coresight-ctcu.yaml | 6 +- .../arm/qcom,coresight-remote-etm.yaml | 4 +- .../bindings/arm/qcom,coresight-tnoc.yaml | 2 +- .../bindings/arm/qcom,coresight-tpda.yaml | 4 +- .../bindings/arm/qcom,coresight-tpdm.yaml | 4 +- 9 files changed, 42 insertions(+), 42 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-dummy-source b/Documentation/ABI/testing/sysfs-bus-coresight-devices-dummy-source index 321e3ee1fc9d..c8c58914116e 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-dummy-source +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-dummy-source @@ -1,7 +1,7 @@ What: /sys/bus/coresight/devices/dummy_source/enable_source Date: Dec 2024 KernelVersion: 6.14 -Contact: Mao Jinlong +Contact: Mao Jinlong Description: (RW) Enable/disable tracing of dummy source. A sink should be activated before enabling the source. The path of coresight components linking the source to the sink is configured and managed automatically by the @@ -10,7 +10,7 @@ Description: (RW) Enable/disable tracing of dummy source. A sink should be activ What: /sys/bus/coresight/devices/dummy_source/traceid Date: Dec 2024 KernelVersion: 6.14 -Contact: Mao Jinlong +Contact: Mao Jinlong Description: (R) Show the trace ID that will appear in the trace stream coming from this trace entity. diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm index 98f1c6545027..f8016df64532 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm @@ -1,7 +1,7 @@ What: /sys/bus/coresight/devices//integration_test Date: January 2023 KernelVersion: 6.2 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (Write) Run integration test for tpdm. Integration test will generate test data for tpdm. It can help to make @@ -15,7 +15,7 @@ Description: What: /sys/bus/coresight/devices//reset_dataset Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (Write) Reset the dataset of the tpdm. @@ -25,7 +25,7 @@ Description: What: /sys/bus/coresight/devices//dsb_trig_type Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (RW) Set/Get the trigger type of the DSB for tpdm. @@ -36,7 +36,7 @@ Description: What: /sys/bus/coresight/devices//dsb_trig_ts Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (RW) Set/Get the trigger timestamp of the DSB for tpdm. @@ -47,7 +47,7 @@ Description: What: /sys/bus/coresight/devices//dsb_mode Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (RW) Set/Get the programming mode of the DSB for tpdm. @@ -61,7 +61,7 @@ Description: What: /sys/bus/coresight/devices//dsb_edge/ctrl_idx Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (RW) Set/Get the index number of the edge detection for the DSB subunit TPDM. Since there are at most 256 edge detections, this @@ -70,7 +70,7 @@ Description: What: /sys/bus/coresight/devices//dsb_edge/ctrl_val Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: Write a data to control the edge detection corresponding to the index number. Before writing data to this sysfs file, @@ -86,7 +86,7 @@ Description: What: /sys/bus/coresight/devices//dsb_edge/ctrl_mask Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: Write a data to mask the edge detection corresponding to the index number. Before writing data to this sysfs file, "ctrl_idx" should @@ -98,21 +98,21 @@ Description: What: /sys/bus/coresight/devices//dsb_edge/edcr[0:15] Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: Read a set of the edge control value of the DSB in TPDM. What: /sys/bus/coresight/devices//dsb_edge/edcmr[0:7] Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: Read a set of the edge control mask of the DSB in TPDM. What: /sys/bus/coresight/devices//dsb_trig_patt/xpr[0:7] Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (RW) Set/Get the value of the trigger pattern for the DSB subunit TPDM. @@ -120,7 +120,7 @@ Description: What: /sys/bus/coresight/devices//dsb_trig_patt/xpmr[0:7] Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (RW) Set/Get the mask of the trigger pattern for the DSB subunit TPDM. @@ -128,21 +128,21 @@ Description: What: /sys/bus/coresight/devices//dsb_patt/tpr[0:7] Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (RW) Set/Get the value of the pattern for the DSB subunit TPDM. What: /sys/bus/coresight/devices//dsb_patt/tpmr[0:7] Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (RW) Set/Get the mask of the pattern for the DSB subunit TPDM. What: /sys/bus/coresight/devices//dsb_patt/enable_ts Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (Write) Set the pattern timestamp of DSB tpdm. Read the pattern timestamp of DSB tpdm. @@ -154,7 +154,7 @@ Description: What: /sys/bus/coresight/devices//dsb_patt/set_type Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (Write) Set the pattern type of DSB tpdm. Read the pattern type of DSB tpdm. @@ -166,7 +166,7 @@ Description: What: /sys/bus/coresight/devices//dsb_msr/msr[0:31] Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (RW) Set/Get the MSR(mux select register) for the DSB subunit TPDM. @@ -174,7 +174,7 @@ Description: What: /sys/bus/coresight/devices//cmb_mode Date: January 2024 KernelVersion: 6.9 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (Write) Set the data collection mode of CMB tpdm. Continuous change creates CMB data set elements on every CMBCLK edge. Trace-on-change creates CMB data set elements only when a new @@ -188,7 +188,7 @@ Description: (Write) Set the data collection mode of CMB tpdm. Continuous What: /sys/bus/coresight/devices//cmb_trig_patt/xpr[0:1] Date: January 2024 KernelVersion: 6.9 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (RW) Set/Get the value of the trigger pattern for the CMB subunit TPDM. @@ -196,7 +196,7 @@ Description: What: /sys/bus/coresight/devices//cmb_trig_patt/xpmr[0:1] Date: January 2024 KernelVersion: 6.9 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (RW) Set/Get the mask of the trigger pattern for the CMB subunit TPDM. @@ -204,21 +204,21 @@ Description: What: /sys/bus/coresight/devices//dsb_patt/tpr[0:1] Date: January 2024 KernelVersion: 6.9 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (RW) Set/Get the value of the pattern for the CMB subunit TPDM. What: /sys/bus/coresight/devices//dsb_patt/tpmr[0:1] Date: January 2024 KernelVersion: 6.9 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (RW) Set/Get the mask of the pattern for the CMB subunit TPDM. What: /sys/bus/coresight/devices//cmb_patt/enable_ts Date: January 2024 KernelVersion: 6.9 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (Write) Set the pattern timestamp of CMB tpdm. Read the pattern timestamp of CMB tpdm. @@ -230,7 +230,7 @@ Description: What: /sys/bus/coresight/devices//cmb_trig_ts Date: January 2024 KernelVersion: 6.9 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (RW) Set/Get the trigger timestamp of the CMB for tpdm. @@ -241,7 +241,7 @@ Description: What: /sys/bus/coresight/devices//cmb_ts_all Date: January 2024 KernelVersion: 6.9 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (RW) Read or write the status of timestamp upon all interface. Only value 0 and 1 can be written to this node. Set this node to 1 to request @@ -253,7 +253,7 @@ Description: What: /sys/bus/coresight/devices//cmb_msr/msr[0:31] Date: January 2024 KernelVersion: 6.9 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (RW) Set/Get the MSR(mux select register) for the CMB subunit TPDM. @@ -261,7 +261,7 @@ Description: What: /sys/bus/coresight/devices//mcmb_trig_lane Date: Feb 2025 KernelVersion 6.15 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (RW) Set/Get which lane participates in the output pattern match cross trigger mechanism for the MCMB subunit TPDM. @@ -269,7 +269,7 @@ Description: What: /sys/bus/coresight/devices//mcmb_lanes_select Date: Feb 2025 KernelVersion 6.15 -Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Contact: Jinlong Mao , Tao Zhang Description: (RW) Set/Get the enablement of the individual lane. diff --git a/Documentation/devicetree/bindings/arm/arm,coresight-dummy-sink.yaml b/Documentation/devicetree/bindings/arm/arm,coresight-dummy-sink.yaml index ed091dc0c10a..206681ccaa4c 100644 --- a/Documentation/devicetree/bindings/arm/arm,coresight-dummy-sink.yaml +++ b/Documentation/devicetree/bindings/arm/arm,coresight-dummy-sink.yaml @@ -31,7 +31,7 @@ maintainers: - Mike Leach - Suzuki K Poulose - James Clark - - Mao Jinlong + - Mao Jinlong - Hao Zhang properties: diff --git a/Documentation/devicetree/bindings/arm/arm,coresight-dummy-source.yaml b/Documentation/devicetree/bindings/arm/arm,coresight-dummy-source.yaml index 78337be42b55..0b1e12ae95c3 100644 --- a/Documentation/devicetree/bindings/arm/arm,coresight-dummy-source.yaml +++ b/Documentation/devicetree/bindings/arm/arm,coresight-dummy-source.yaml @@ -30,7 +30,7 @@ maintainers: - Mike Leach - Suzuki K Poulose - James Clark - - Mao Jinlong + - Mao Jinlong - Hao Zhang properties: diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-ctcu.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-ctcu.yaml index c969c16c21ef..2544fc1f71f5 100644 --- a/Documentation/devicetree/bindings/arm/qcom,coresight-ctcu.yaml +++ b/Documentation/devicetree/bindings/arm/qcom,coresight-ctcu.yaml @@ -7,9 +7,9 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: CoreSight TMC Control Unit maintainers: - - Yuanfang Zhang - - Mao Jinlong - - Jie Gan + - Yuanfang Zhang + - Mao Jinlong + - Jie Gan description: | The Trace Memory Controller(TMC) is used for Embedded Trace Buffer(ETB), diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-remote-etm.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-remote-etm.yaml index ffe613efeabe..e3a32f30551c 100644 --- a/Documentation/devicetree/bindings/arm/qcom,coresight-remote-etm.yaml +++ b/Documentation/devicetree/bindings/arm/qcom,coresight-remote-etm.yaml @@ -7,8 +7,8 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Qualcomm Coresight Remote ETM(Embedded Trace Macrocell) maintainers: - - Jinlong Mao - - Tao Zhang + - Jinlong Mao + - Tao Zhang description: Support for ETM trace collection on remote processor using coresight diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-tnoc.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-tnoc.yaml index 9d1c93a9ade3..ef648a15b806 100644 --- a/Documentation/devicetree/bindings/arm/qcom,coresight-tnoc.yaml +++ b/Documentation/devicetree/bindings/arm/qcom,coresight-tnoc.yaml @@ -7,7 +7,7 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Qualcomm Trace Network On Chip - TNOC maintainers: - - Yuanfang Zhang + - Yuanfang Zhang description: > The Trace Network On Chip (TNOC) is an integration hierarchy hardware diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml index a48c9ac3eaa9..70d297b054c3 100644 --- a/Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml +++ b/Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml @@ -33,8 +33,8 @@ description: | to sink. maintainers: - - Mao Jinlong - - Tao Zhang + - Mao Jinlong + - Tao Zhang # Need a custom select here or 'arm,primecell' will match on lots of nodes select: diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-tpdm.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-tpdm.yaml index c349306f0d52..152403f548c3 100644 --- a/Documentation/devicetree/bindings/arm/qcom,coresight-tpdm.yaml +++ b/Documentation/devicetree/bindings/arm/qcom,coresight-tpdm.yaml @@ -19,8 +19,8 @@ description: | sources and send it to a TPDA for packetization, timestamping, and funneling. maintainers: - - Mao Jinlong - - Tao Zhang + - Mao Jinlong + - Tao Zhang # Need a custom select here or 'arm,primecell' will match on lots of nodes select: From 51cd1fb70e08802904cd990b9b446125ee34de13 Mon Sep 17 00:00:00 2001 From: Jie Gan Date: Mon, 3 Nov 2025 15:06:21 +0800 Subject: [PATCH 003/297] dt-bindings: arm: add CTCU device for monaco The CTCU device for monaco shares the same configurations as SA8775p. Add a fallback to enable the CTCU for monaco to utilize the compitable of the SA8775p. Reviewed-by: Krzysztof Kozlowski Acked-by: Suzuki K Poulose Reviewed-by: Bjorn Andersson Signed-off-by: Jie Gan Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251103-enable-ctcu-for-monaco-v4-1-92ff83201584@oss.qualcomm.com --- .../devicetree/bindings/arm/qcom,coresight-ctcu.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-ctcu.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-ctcu.yaml index 2544fc1f71f5..e002f87361ad 100644 --- a/Documentation/devicetree/bindings/arm/qcom,coresight-ctcu.yaml +++ b/Documentation/devicetree/bindings/arm/qcom,coresight-ctcu.yaml @@ -26,8 +26,13 @@ description: | properties: compatible: - enum: - - qcom,sa8775p-ctcu + oneOf: + - items: + - enum: + - qcom,qcs8300-ctcu + - const: qcom,sa8775p-ctcu + - enum: + - qcom,sa8775p-ctcu reg: maxItems: 1 From c141c8221bc5089de915d9f26044df892c343c7e Mon Sep 17 00:00:00 2001 From: Romain Gantois Date: Thu, 27 Nov 2025 16:58:48 +0100 Subject: [PATCH 004/297] fpga: of-fpga-region: Fail if any bridge is missing When parsing the region bridge list from the "fpga-bridges" device tree property, the of-fpga-region driver will silently ignore bridges which fail to be obtained, for example due to a missing bridge driver or invalid phandle. This can lead to hardware issues if a region bridge stays coupled when partial programming is performed. Fail if any of the bridges specified in "fpga-bridges" cannot be obtained. Signed-off-by: Romain Gantois Link: https://lore.kernel.org/r/20251127-of-fpga-region-fail-if-bridges-not-found-v1-1-ca674f8d07eb@bootlin.com Reviewed-by: Xu Yilun Signed-off-by: Xu Yilun --- drivers/fpga/of-fpga-region.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/fpga/of-fpga-region.c b/drivers/fpga/of-fpga-region.c index 43db4bb77138..caa091224dc5 100644 --- a/drivers/fpga/of-fpga-region.c +++ b/drivers/fpga/of-fpga-region.c @@ -83,7 +83,7 @@ static struct fpga_manager *of_fpga_region_get_mgr(struct device_node *np) * done with the bridges. * * Return: 0 for success (even if there are no bridges specified) - * or -EBUSY if any of the bridges are in use. + * or an error code if any of the bridges are not available. */ static int of_fpga_region_get_bridges(struct fpga_region *region) { @@ -130,10 +130,10 @@ static int of_fpga_region_get_bridges(struct fpga_region *region) ®ion->bridge_list); of_node_put(br); - /* If any of the bridges are in use, give up */ - if (ret == -EBUSY) { + /* If any of the bridges are not available, give up */ + if (ret) { fpga_bridges_put(®ion->bridge_list); - return -EBUSY; + return ret; } } From 996a590dc6905db52e4f70569134ec479e2a9ac4 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Mon, 15 Dec 2025 09:28:29 +0100 Subject: [PATCH 005/297] fpga: xilinx: Switch Michal Simek's email to new one @xilinx.com is still working but better to switch to new amd.com after AMD/Xilinx acquisition. Signed-off-by: Michal Simek Link: https://lore.kernel.org/r/49de9536c9952a3a36bb996f2fb0a1f0efb1ba6f.1765787307.git.michal.simek@amd.com Reviewed-by: Xu Yilun Signed-off-by: Xu Yilun --- drivers/fpga/xilinx-pr-decoupler.c | 2 +- drivers/fpga/zynq-fpga.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/fpga/xilinx-pr-decoupler.c b/drivers/fpga/xilinx-pr-decoupler.c index 822751fad18a..6994d68e9036 100644 --- a/drivers/fpga/xilinx-pr-decoupler.c +++ b/drivers/fpga/xilinx-pr-decoupler.c @@ -173,5 +173,5 @@ module_platform_driver(xlnx_pr_decoupler_driver); MODULE_DESCRIPTION("Xilinx Partial Reconfiguration Decoupler"); MODULE_AUTHOR("Moritz Fischer "); -MODULE_AUTHOR("Michal Simek "); +MODULE_AUTHOR("Michal Simek "); MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/zynq-fpga.c b/drivers/fpga/zynq-fpga.c index b7629a0e4813..9d1d599ef718 100644 --- a/drivers/fpga/zynq-fpga.c +++ b/drivers/fpga/zynq-fpga.c @@ -652,6 +652,6 @@ static struct platform_driver zynq_fpga_driver = { module_platform_driver(zynq_fpga_driver); MODULE_AUTHOR("Moritz Fischer "); -MODULE_AUTHOR("Michal Simek "); +MODULE_AUTHOR("Michal Simek "); MODULE_DESCRIPTION("Xilinx Zynq FPGA Manager"); MODULE_LICENSE("GPL v2"); From 267f53140c9d0bf270bbe0148082e9b8e5011273 Mon Sep 17 00:00:00 2001 From: Thadeu Lima de Souza Cascardo Date: Mon, 15 Dec 2025 16:05:50 -0300 Subject: [PATCH 006/297] fpga: dfl: use subsys_initcall to allow built-in drivers to be added The dfl code adds a bus. If it is built-in and there is a built-in driver as well, the dfl module_init may be called after the driver module_init, leading to a failure to register the driver as the bus has not been added yet. Use subsys_initcall, which guarantees it will be called before the drivers init code. Without the fix, we see failures like this: [ 0.479475] Driver 'intel-m10-bmc' was unable to register with bus_type 'dfl' because the bus was not initialized. Cc: stable@vger.kernel.org Fixes: 9ba3a0aa09fe ("fpga: dfl: create a dfl bus type to support DFL devices") Signed-off-by: Thadeu Lima de Souza Cascardo Link: https://lore.kernel.org/r/20251215-dfl_subsys-v1-1-21807bad6b10@igalia.com Reviewed-by: Xu Yilun Signed-off-by: Xu Yilun --- drivers/fpga/dfl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/fpga/dfl.c b/drivers/fpga/dfl.c index 7022657243c0..449c3a082e23 100644 --- a/drivers/fpga/dfl.c +++ b/drivers/fpga/dfl.c @@ -2018,7 +2018,7 @@ static void __exit dfl_fpga_exit(void) bus_unregister(&dfl_bus_type); } -module_init(dfl_fpga_init); +subsys_initcall(dfl_fpga_init); module_exit(dfl_fpga_exit); MODULE_DESCRIPTION("FPGA Device Feature List (DFL) Support"); From c7f9c36b7921235453828bfff1ad6f081267a7ec Mon Sep 17 00:00:00 2001 From: Tomas Borquez Date: Fri, 5 Dec 2025 17:27:41 -0300 Subject: [PATCH 007/297] staging: iio: ad9832: remove platform_data support Remove legacy platform_data support as there are no in tree users and this approach belongs to a long gone era. The policy decision on what to output is a userspace problem, not something that should be provided from firmware. The driver now initializes the device to a safe state (SLEEP|RESET|CLR) outputting nothing. Userspace can configure the desired frequencies and phases via the existing sysfs attributes once the device is ready to be used. Original discussion started here [1]. Link: https://lore.kernel.org/linux-iio/20250628161040.3d21e2c4@jic23-huawei/ #[1] Suggested-by: Jonathan Cameron Signed-off-by: Tomas Borquez Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/staging/iio/frequency/ad9832.c | 32 ------------------------- drivers/staging/iio/frequency/ad9832.h | 33 -------------------------- 2 files changed, 65 deletions(-) delete mode 100644 drivers/staging/iio/frequency/ad9832.h diff --git a/drivers/staging/iio/frequency/ad9832.c b/drivers/staging/iio/frequency/ad9832.c index 49388da5a684..e2ad3e5a7a8e 100644 --- a/drivers/staging/iio/frequency/ad9832.c +++ b/drivers/staging/iio/frequency/ad9832.c @@ -23,8 +23,6 @@ #include #include -#include "ad9832.h" - #include "dds.h" /* Registers */ @@ -299,16 +297,10 @@ static const struct iio_info ad9832_info = { static int ad9832_probe(struct spi_device *spi) { - struct ad9832_platform_data *pdata = dev_get_platdata(&spi->dev); struct iio_dev *indio_dev; struct ad9832_state *st; int ret; - if (!pdata) { - dev_dbg(&spi->dev, "no platform data?\n"); - return -ENODEV; - } - indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); if (!indio_dev) return -ENOMEM; @@ -379,30 +371,6 @@ static int ad9832_probe(struct spi_device *spi) return ret; } - ret = ad9832_write_frequency(st, AD9832_FREQ0HM, pdata->freq0); - if (ret) - return ret; - - ret = ad9832_write_frequency(st, AD9832_FREQ1HM, pdata->freq1); - if (ret) - return ret; - - ret = ad9832_write_phase(st, AD9832_PHASE0H, pdata->phase0); - if (ret) - return ret; - - ret = ad9832_write_phase(st, AD9832_PHASE1H, pdata->phase1); - if (ret) - return ret; - - ret = ad9832_write_phase(st, AD9832_PHASE2H, pdata->phase2); - if (ret) - return ret; - - ret = ad9832_write_phase(st, AD9832_PHASE3H, pdata->phase3); - if (ret) - return ret; - return devm_iio_device_register(&spi->dev, indio_dev); } diff --git a/drivers/staging/iio/frequency/ad9832.h b/drivers/staging/iio/frequency/ad9832.h deleted file mode 100644 index d0d840edb8d2..000000000000 --- a/drivers/staging/iio/frequency/ad9832.h +++ /dev/null @@ -1,33 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * AD9832 SPI DDS driver - * - * Copyright 2011 Analog Devices Inc. - */ -#ifndef IIO_DDS_AD9832_H_ -#define IIO_DDS_AD9832_H_ - -/* - * TODO: struct ad9832_platform_data needs to go into include/linux/iio - */ - -/** - * struct ad9832_platform_data - platform specific information - * @freq0: power up freq0 tuning word in Hz - * @freq1: power up freq1 tuning word in Hz - * @phase0: power up phase0 value [0..4095] correlates with 0..2PI - * @phase1: power up phase1 value [0..4095] correlates with 0..2PI - * @phase2: power up phase2 value [0..4095] correlates with 0..2PI - * @phase3: power up phase3 value [0..4095] correlates with 0..2PI - */ - -struct ad9832_platform_data { - unsigned long freq0; - unsigned long freq1; - unsigned short phase0; - unsigned short phase1; - unsigned short phase2; - unsigned short phase3; -}; - -#endif /* IIO_DDS_AD9832_H_ */ From 0820dd9f51884828777f68daa30bfacacbceaf36 Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Wed, 3 Dec 2025 09:28:12 +0000 Subject: [PATCH 008/297] iio: adc: ad9467: support write/read offset via _calibbias MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support configuring output calibration value. Among the devices currently supported by this driver, this setting is specific to ad9434. The offset can be used to calibrate the output against a known input. The register is called offset, but the procedure is best mapped internally with calibbias operation. Reviewed-by: David Lechner Signed-off-by: Tomas Melin Reviewed-by: Nuno Sá Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad9467.c | 60 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/ad9467.c b/drivers/iio/adc/ad9467.c index f7a9f46ea0dc..995d811d4fbe 100644 --- a/drivers/iio/adc/ad9467.c +++ b/drivers/iio/adc/ad9467.c @@ -145,6 +145,7 @@ struct ad9467_chip_info { unsigned int num_lanes; unsigned int dco_en; unsigned int test_points; + const int *offset_range; /* data clock output */ bool has_dco; bool has_dco_invert; @@ -234,6 +235,10 @@ static int ad9467_reg_access(struct iio_dev *indio_dev, unsigned int reg, return 0; } +static const int ad9434_offset_range[] = { + -128, 1, 127, +}; + static const unsigned int ad9265_scale_table[][2] = { {1250, 0x00}, {1500, 0x40}, {1750, 0x80}, {2000, 0xC0}, }; @@ -298,7 +303,24 @@ static void __ad9467_get_scale(struct ad9467_state *st, int index, } static const struct iio_chan_spec ad9434_channels[] = { - AD9467_CHAN(0, BIT(IIO_CHAN_INFO_SCALE), 0, 12, 's'), + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_shared_by_type_available = + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 12, + .storagebits = 16, + }, + }, }; static const struct iio_chan_spec ad9467_channels[] = { @@ -367,6 +389,7 @@ static const struct ad9467_chip_info ad9434_chip_tbl = { .default_output_mode = AD9434_DEF_OUTPUT_MODE, .vref_mask = AD9434_REG_VREF_MASK, .num_lanes = 6, + .offset_range = ad9434_offset_range, }; static const struct ad9467_chip_info ad9265_chip_tbl = { @@ -499,6 +522,33 @@ static int ad9467_set_scale(struct ad9467_state *st, int val, int val2) return -EINVAL; } +static int ad9467_get_offset(struct ad9467_state *st, int *val) +{ + int ret; + + ret = ad9467_spi_read(st, AN877_ADC_REG_OFFSET); + if (ret < 0) + return ret; + *val = ret; + + return IIO_VAL_INT; +} + +static int ad9467_set_offset(struct ad9467_state *st, int val) +{ + int ret; + + if (val < st->info->offset_range[0] || val > st->info->offset_range[2]) + return -EINVAL; + + ret = ad9467_spi_write(st, AN877_ADC_REG_OFFSET, val); + if (ret < 0) + return ret; + + return ad9467_spi_write(st, AN877_ADC_REG_TRANSFER, + AN877_ADC_TRANSFER_SYNC); +} + static int ad9467_outputmode_set(struct ad9467_state *st, unsigned int mode) { int ret; @@ -802,6 +852,8 @@ static int ad9467_read_raw(struct iio_dev *indio_dev, struct ad9467_state *st = iio_priv(indio_dev); switch (m) { + case IIO_CHAN_INFO_CALIBBIAS: + return ad9467_get_offset(st, val); case IIO_CHAN_INFO_SCALE: return ad9467_get_scale(st, val, val2); case IIO_CHAN_INFO_SAMP_FREQ: @@ -836,6 +888,8 @@ static int ad9467_write_raw(struct iio_dev *indio_dev, int ret; switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + return ad9467_set_offset(st, val); case IIO_CHAN_INFO_SCALE: return ad9467_set_scale(st, val, val2); case IIO_CHAN_INFO_SAMP_FREQ: @@ -874,6 +928,10 @@ static int ad9467_read_avail(struct iio_dev *indio_dev, const struct ad9467_chip_info *info = st->info; switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + *type = IIO_VAL_INT; + *vals = info->offset_range; + return IIO_AVAIL_RANGE; case IIO_CHAN_INFO_SCALE: *vals = (const int *)st->scales; *type = IIO_VAL_INT_PLUS_MICRO; From 212234f7bf8ef93c29a2808bcf60465cc1bfff66 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Mon, 1 Dec 2025 11:00:11 +0100 Subject: [PATCH 009/297] iio: imu: st_lsm6dsx: make event_settings more generic The st_lsm6dsx_event_settings structure contains fields specific for one event type (wakeup). In preparation for adding support for more event types, introduce an event id enum and a generic event source structure, and replace wakeup-specific data in struct st_lsm6dsx_event_settings with an array of event source structures. Signed-off-by: Francesco Lavra Reviewed-by: Andy Shevchenko Acked-by: Lorenzo Bianconi Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 20 ++- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 146 ++++++++++++------- 2 files changed, 106 insertions(+), 60 deletions(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index 6405a5367d76..bd2f25cebdf8 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -260,14 +260,22 @@ struct st_lsm6dsx_shub_settings { u8 pause; }; +enum st_lsm6dsx_event_id { + ST_LSM6DSX_EVENT_WAKEUP, + ST_LSM6DSX_EVENT_MAX +}; + +struct st_lsm6dsx_event_src { + struct st_lsm6dsx_reg value; + struct st_lsm6dsx_reg status; + u8 status_x_mask; + u8 status_y_mask; + u8 status_z_mask; +}; + struct st_lsm6dsx_event_settings { struct st_lsm6dsx_reg enable_reg; - struct st_lsm6dsx_reg wakeup_reg; - u8 wakeup_src_reg; - u8 wakeup_src_status_mask; - u8 wakeup_src_z_mask; - u8 wakeup_src_y_mask; - u8 wakeup_src_x_mask; + struct st_lsm6dsx_event_src sources[ST_LSM6DSX_EVENT_MAX]; }; enum st_lsm6dsx_sensor_id { diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index 49ac17806e72..1dc3d4b41b0b 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -379,15 +379,21 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { }, }, .event_settings = { - .wakeup_reg = { - .addr = 0x5B, - .mask = GENMASK(5, 0), + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .status = { + .addr = 0x1b, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, }, - .wakeup_src_reg = 0x1b, - .wakeup_src_status_mask = BIT(3), - .wakeup_src_z_mask = BIT(0), - .wakeup_src_y_mask = BIT(1), - .wakeup_src_x_mask = BIT(2), }, }, { @@ -545,15 +551,21 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { }, }, .event_settings = { - .wakeup_reg = { - .addr = 0x5B, - .mask = GENMASK(5, 0), + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .status = { + .addr = 0x1b, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, }, - .wakeup_src_reg = 0x1b, - .wakeup_src_status_mask = BIT(3), - .wakeup_src_z_mask = BIT(0), - .wakeup_src_y_mask = BIT(1), - .wakeup_src_x_mask = BIT(2), }, }, { @@ -782,15 +794,21 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x58, .mask = BIT(7), }, - .wakeup_reg = { - .addr = 0x5B, - .mask = GENMASK(5, 0), + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .status = { + .addr = 0x1b, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, }, - .wakeup_src_reg = 0x1b, - .wakeup_src_status_mask = BIT(3), - .wakeup_src_z_mask = BIT(0), - .wakeup_src_y_mask = BIT(1), - .wakeup_src_x_mask = BIT(2), }, }, { @@ -1021,15 +1039,21 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x58, .mask = BIT(7), }, - .wakeup_reg = { - .addr = 0x5b, - .mask = GENMASK(5, 0), + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .status = { + .addr = 0x1b, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, }, - .wakeup_src_reg = 0x1b, - .wakeup_src_status_mask = BIT(3), - .wakeup_src_z_mask = BIT(0), - .wakeup_src_y_mask = BIT(1), - .wakeup_src_x_mask = BIT(2), }, }, { @@ -1204,15 +1228,21 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x58, .mask = BIT(7), }, - .wakeup_reg = { - .addr = 0x5B, - .mask = GENMASK(5, 0), + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .status = { + .addr = 0x1b, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, }, - .wakeup_src_reg = 0x1b, - .wakeup_src_status_mask = BIT(3), - .wakeup_src_z_mask = BIT(0), - .wakeup_src_y_mask = BIT(1), - .wakeup_src_x_mask = BIT(2), }, }, { @@ -1412,15 +1442,21 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x50, .mask = BIT(7), }, - .wakeup_reg = { - .addr = 0x5b, - .mask = GENMASK(5, 0), + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .status = { + .addr = 0x45, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, }, - .wakeup_src_reg = 0x45, - .wakeup_src_status_mask = BIT(3), - .wakeup_src_z_mask = BIT(0), - .wakeup_src_y_mask = BIT(1), - .wakeup_src_x_mask = BIT(2), }, }, { @@ -1932,7 +1968,7 @@ st_lsm6dsx_write_event(struct iio_dev *iio_dev, if (val < 0 || val > 31) return -EINVAL; - reg = &hw->settings->event_settings.wakeup_reg; + reg = &hw->settings->event_settings.sources[ST_LSM6DSX_EVENT_WAKEUP].value; data = ST_LSM6DSX_SHIFT_VAL(val, reg->mask); err = st_lsm6dsx_update_bits_locked(hw, reg->addr, reg->mask, data); @@ -2410,6 +2446,7 @@ static bool st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) { const struct st_lsm6dsx_event_settings *event_settings; + const struct st_lsm6dsx_event_src *src; int err, data; s64 timestamp; @@ -2417,13 +2454,14 @@ st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) return false; event_settings = &hw->settings->event_settings; - err = st_lsm6dsx_read_locked(hw, event_settings->wakeup_src_reg, + src = &event_settings->sources[ST_LSM6DSX_EVENT_WAKEUP]; + err = st_lsm6dsx_read_locked(hw, src->status.addr, &data, sizeof(data)); if (err < 0) return false; timestamp = iio_get_time_ns(hw->iio_devs[ST_LSM6DSX_ID_ACC]); - if ((data & hw->settings->event_settings.wakeup_src_z_mask) && + if ((data & src->status_z_mask) && (hw->enable_event & BIT(IIO_MOD_Z))) iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], IIO_MOD_EVENT_CODE(IIO_ACCEL, @@ -2433,7 +2471,7 @@ st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) IIO_EV_DIR_EITHER), timestamp); - if ((data & hw->settings->event_settings.wakeup_src_y_mask) && + if ((data & src->status_y_mask) && (hw->enable_event & BIT(IIO_MOD_Y))) iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], IIO_MOD_EVENT_CODE(IIO_ACCEL, @@ -2443,7 +2481,7 @@ st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) IIO_EV_DIR_EITHER), timestamp); - if ((data & hw->settings->event_settings.wakeup_src_x_mask) && + if ((data & src->status_x_mask) && (hw->enable_event & BIT(IIO_MOD_X))) iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], IIO_MOD_EVENT_CODE(IIO_ACCEL, @@ -2453,7 +2491,7 @@ st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) IIO_EV_DIR_EITHER), timestamp); - return data & event_settings->wakeup_src_status_mask; + return data & src->status.mask; } static irqreturn_t st_lsm6dsx_handler_thread(int irq, void *private) From 87c3e0c138a7f01efb5de5e226055ab66345d3d0 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Mon, 1 Dec 2025 11:00:12 +0100 Subject: [PATCH 010/297] iio: imu: st_lsm6dsx: move wakeup event enable mask to event_src The mask value being assigned to the irq1_func and irq2_func fields of the irq_config struct is specific to a single event source (i.e. the wakeup event), and as such it should be separate from the definition of the interrupt function registers, which cover multiple event sources. In preparation for adding support for more event types, change the irq1_func and irq2_func type from an {address, mask} pair to an address, and move the mask value to a new field of struct st_lsm6dsx_event_src. No functional changes. Signed-off-by: Francesco Lavra Reviewed-by: Andy Shevchenko Acked-by: Lorenzo Bianconi Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 7 +- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 80 +++++++------------- 2 files changed, 30 insertions(+), 57 deletions(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index bd2f25cebdf8..137e385110b5 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -267,6 +267,7 @@ enum st_lsm6dsx_event_id { struct st_lsm6dsx_event_src { struct st_lsm6dsx_reg value; + u8 enable_mask; struct st_lsm6dsx_reg status; u8 status_x_mask; u8 status_y_mask; @@ -361,8 +362,8 @@ struct st_lsm6dsx_settings { struct { struct st_lsm6dsx_reg irq1; struct st_lsm6dsx_reg irq2; - struct st_lsm6dsx_reg irq1_func; - struct st_lsm6dsx_reg irq2_func; + u8 irq1_func; + u8 irq2_func; struct st_lsm6dsx_reg lir; struct st_lsm6dsx_reg clear_on_read; struct st_lsm6dsx_reg hla; @@ -461,7 +462,7 @@ struct st_lsm6dsx_hw { u8 ts_sip; u8 sip; - const struct st_lsm6dsx_reg *irq_routing; + u8 irq_routing; u8 event_threshold; u8 enable_event; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index 1dc3d4b41b0b..39ae79991b91 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -319,14 +319,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x58, .mask = BIT(0), }, - .irq1_func = { - .addr = 0x5e, - .mask = BIT(5), - }, - .irq2_func = { - .addr = 0x5f, - .mask = BIT(5), - }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, .hla = { .addr = 0x12, .mask = BIT(5), @@ -385,6 +379,7 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x5b, .mask = GENMASK(5, 0), }, + .enable_mask = BIT(5), .status = { .addr = 0x1b, .mask = BIT(3), @@ -491,14 +486,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x58, .mask = BIT(0), }, - .irq1_func = { - .addr = 0x5e, - .mask = BIT(5), - }, - .irq2_func = { - .addr = 0x5f, - .mask = BIT(5), - }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, .hla = { .addr = 0x12, .mask = BIT(5), @@ -557,6 +546,7 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x5b, .mask = GENMASK(5, 0), }, + .enable_mask = BIT(5), .status = { .addr = 0x1b, .mask = BIT(3), @@ -693,14 +683,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x58, .mask = BIT(0), }, - .irq1_func = { - .addr = 0x5e, - .mask = BIT(5), - }, - .irq2_func = { - .addr = 0x5f, - .mask = BIT(5), - }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, .hla = { .addr = 0x12, .mask = BIT(5), @@ -800,6 +784,7 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x5b, .mask = GENMASK(5, 0), }, + .enable_mask = BIT(5), .status = { .addr = 0x1b, .mask = BIT(3), @@ -948,14 +933,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x56, .mask = BIT(6), }, - .irq1_func = { - .addr = 0x5e, - .mask = BIT(5), - }, - .irq2_func = { - .addr = 0x5f, - .mask = BIT(5), - }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, .hla = { .addr = 0x12, .mask = BIT(5), @@ -1045,6 +1024,7 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x5b, .mask = GENMASK(5, 0), }, + .enable_mask = BIT(5), .status = { .addr = 0x1b, .mask = BIT(3), @@ -1169,14 +1149,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x56, .mask = BIT(6), }, - .irq1_func = { - .addr = 0x5e, - .mask = BIT(5), - }, - .irq2_func = { - .addr = 0x5f, - .mask = BIT(5), - }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, .hla = { .addr = 0x12, .mask = BIT(5), @@ -1234,6 +1208,7 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x5b, .mask = GENMASK(5, 0), }, + .enable_mask = BIT(5), .status = { .addr = 0x1b, .mask = BIT(3), @@ -1352,14 +1327,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x56, .mask = BIT(0), }, - .irq1_func = { - .addr = 0x5e, - .mask = BIT(5), - }, - .irq2_func = { - .addr = 0x5f, - .mask = BIT(5), - }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, .hla = { .addr = 0x03, .mask = BIT(4), @@ -1448,6 +1417,7 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x5b, .mask = GENMASK(5, 0), }, + .enable_mask = BIT(5), .status = { .addr = 0x45, .mask = BIT(3), @@ -1908,10 +1878,11 @@ static int st_lsm6dsx_write_raw(struct iio_dev *iio_dev, static int st_lsm6dsx_event_setup(struct st_lsm6dsx_hw *hw, bool state) { const struct st_lsm6dsx_reg *reg; + const struct st_lsm6dsx_event_src *src; unsigned int data; int err; - if (!hw->settings->irq_config.irq1_func.addr) + if (!hw->irq_routing) return -ENOTSUPP; reg = &hw->settings->event_settings.enable_reg; @@ -1924,9 +1895,10 @@ static int st_lsm6dsx_event_setup(struct st_lsm6dsx_hw *hw, bool state) } /* Enable wakeup interrupt */ - data = ST_LSM6DSX_SHIFT_VAL(state, hw->irq_routing->mask); - return st_lsm6dsx_update_bits_locked(hw, hw->irq_routing->addr, - hw->irq_routing->mask, data); + src = &hw->settings->event_settings.sources[ST_LSM6DSX_EVENT_WAKEUP]; + data = ST_LSM6DSX_SHIFT_VAL(state, src->enable_mask); + return st_lsm6dsx_update_bits_locked(hw, hw->irq_routing, + src->enable_mask, data); } static int st_lsm6dsx_read_event(struct iio_dev *iio_dev, @@ -2180,11 +2152,11 @@ st_lsm6dsx_get_drdy_reg(struct st_lsm6dsx_hw *hw, switch (drdy_pin) { case 1: - hw->irq_routing = &hw->settings->irq_config.irq1_func; + hw->irq_routing = hw->settings->irq_config.irq1_func; *drdy_reg = &hw->settings->irq_config.irq1; break; case 2: - hw->irq_routing = &hw->settings->irq_config.irq2_func; + hw->irq_routing = hw->settings->irq_config.irq2_func; *drdy_reg = &hw->settings->irq_config.irq2; break; default: From da6279f7587d55171d891ecb3dd1897cad5cad65 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Mon, 1 Dec 2025 11:00:13 +0100 Subject: [PATCH 011/297] iio: imu: st_lsm6dsx: rework code to check for enabled events The enable_event field in struct st_lsm6dsx_hw does not lend itself well to handling multiple event sources, so it will have to be modified to add support for more event sources. As a preparatory step, remove references to this field from code that does not deal with event management; rework the st_lsm6dsx_check_events() function so that it returns whether any events are currently enabled on a given sensor. Signed-off-by: Francesco Lavra Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index 39ae79991b91..ba2df45b77bb 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -1753,11 +1753,11 @@ __st_lsm6dsx_sensor_set_enable(struct st_lsm6dsx_sensor *sensor, } static int -st_lsm6dsx_check_events(struct st_lsm6dsx_sensor *sensor, bool enable) +st_lsm6dsx_check_events(struct st_lsm6dsx_sensor *sensor) { struct st_lsm6dsx_hw *hw = sensor->hw; - if (sensor->id == ST_LSM6DSX_ID_GYRO || enable) + if (sensor->id != ST_LSM6DSX_ID_ACC) return 0; return hw->enable_event; @@ -1766,7 +1766,7 @@ st_lsm6dsx_check_events(struct st_lsm6dsx_sensor *sensor, bool enable) int st_lsm6dsx_sensor_set_enable(struct st_lsm6dsx_sensor *sensor, bool enable) { - if (st_lsm6dsx_check_events(sensor, enable)) + if (st_lsm6dsx_check_events(sensor)) return 0; return __st_lsm6dsx_sensor_set_enable(sensor, enable); @@ -1794,11 +1794,9 @@ static int st_lsm6dsx_read_oneshot(struct st_lsm6dsx_sensor *sensor, if (err < 0) return err; - if (!hw->enable_event) { - err = st_lsm6dsx_sensor_set_enable(sensor, false); - if (err < 0) - return err; - } + err = st_lsm6dsx_sensor_set_enable(sensor, false); + if (err < 0) + return err; *val = (s16)le16_to_cpu(data); @@ -2752,7 +2750,7 @@ static int st_lsm6dsx_suspend(struct device *dev) continue; if (device_may_wakeup(dev) && - sensor->id == ST_LSM6DSX_ID_ACC && hw->enable_event) { + st_lsm6dsx_check_events(sensor)) { /* Enable wake from IRQ */ enable_irq_wake(hw->irq); continue; @@ -2783,7 +2781,7 @@ static int st_lsm6dsx_resume(struct device *dev) sensor = iio_priv(hw->iio_devs[i]); if (device_may_wakeup(dev) && - sensor->id == ST_LSM6DSX_ID_ACC && hw->enable_event) + st_lsm6dsx_check_events(sensor)) disable_irq_wake(hw->irq); if (!(hw->suspend_mask & BIT(sensor->id))) From b008b1ff0ce005e998bb4c7e876ffa2d31a1e511 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Mon, 1 Dec 2025 11:00:14 +0100 Subject: [PATCH 012/297] iio: imu: st_lsm6dsx: remove event_threshold field from hw struct This field is used to store the wakeup event detection threshold value. When adding support for more event types, some of which may have different threshold values for different axes, storing all threshold values for all event sources would be cumbersome. Thus, remove this field altogether, and read the currently configured value from the sensor when requested by userspace. Signed-off-by: Francesco Lavra Reviewed-by: Andy Shevchenko Acked-by: Lorenzo Bianconi Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 3 +-- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 12 +++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index 137e385110b5..784434f7098f 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -79,6 +79,7 @@ enum st_lsm6dsx_hw_id { #define ST_LSM6DSX_MAX_TAGGED_WORD_LEN ((32 / ST_LSM6DSX_TAGGED_SAMPLE_SIZE) \ * ST_LSM6DSX_TAGGED_SAMPLE_SIZE) #define ST_LSM6DSX_SHIFT_VAL(val, mask) (((val) << __ffs(mask)) & (mask)) +#define st_lsm6dsx_field_get(mask, reg) ((reg & mask) >> __ffs(mask)) #define ST_LSM6DSX_CHANNEL_ACC(chan_type, addr, mod, scan_idx) \ { \ @@ -439,7 +440,6 @@ struct st_lsm6dsx_sensor { * @sip: Total number of samples (acc/gyro/ts) in a given pattern. * @buff: Device read buffer. * @irq_routing: pointer to interrupt routing configuration. - * @event_threshold: wakeup event threshold. * @enable_event: enabled event bitmask. * @iio_devs: Pointers to acc/gyro iio_dev instances. * @settings: Pointer to the specific sensor settings in use. @@ -463,7 +463,6 @@ struct st_lsm6dsx_hw { u8 sip; u8 irq_routing; - u8 event_threshold; u8 enable_event; u8 *buff; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index ba2df45b77bb..129f974a038c 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -1908,12 +1908,20 @@ static int st_lsm6dsx_read_event(struct iio_dev *iio_dev, { struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); struct st_lsm6dsx_hw *hw = sensor->hw; + const struct st_lsm6dsx_reg *reg; + u8 data; + int err; if (type != IIO_EV_TYPE_THRESH) return -EINVAL; + reg = &hw->settings->event_settings.sources[ST_LSM6DSX_EVENT_WAKEUP].value; + err = st_lsm6dsx_read_locked(hw, reg->addr, &data, sizeof(data)); + if (err < 0) + return err; + *val2 = 0; - *val = hw->event_threshold; + *val = st_lsm6dsx_field_get(reg->mask, data); return IIO_VAL_INT; } @@ -1945,8 +1953,6 @@ st_lsm6dsx_write_event(struct iio_dev *iio_dev, if (err < 0) return -EINVAL; - hw->event_threshold = val; - return 0; } From c93e8f091baca406981e0468264ed8747285cae6 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Mon, 1 Dec 2025 11:00:15 +0100 Subject: [PATCH 013/297] iio: imu: st_lsm6dsx: make event management functions generic In preparation for adding support for more event types, use an array indexed by event ID instead of a scalar value to store enabled events, and refactor the functions to configure and report events so that their implementation is not specific for wakeup events. Move the logic to update the global event interrupt enable flag from st_lsm6dsx_event_setup() to its calling function, so that it can take into account also event sources different from the source being configured. While changing the signature of the st_lsm6dsx_event_setup() function, opportunistically add the currently unused `axis` parameter, which will be used when adding support for enabling and disabling events on a per axis basis. Signed-off-by: Francesco Lavra Reviewed-by: Andy Shevchenko Acked-by: Lorenzo Bianconi Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 2 +- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 150 ++++++++++++++----- 2 files changed, 110 insertions(+), 42 deletions(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index 784434f7098f..b5e8fc85e346 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -463,7 +463,7 @@ struct st_lsm6dsx_hw { u8 sip; u8 irq_routing; - u8 enable_event; + u8 enable_event[ST_LSM6DSX_EVENT_MAX]; u8 *buff; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index 129f974a038c..721abaf5315d 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -1756,11 +1756,16 @@ static int st_lsm6dsx_check_events(struct st_lsm6dsx_sensor *sensor) { struct st_lsm6dsx_hw *hw = sensor->hw; + int event; if (sensor->id != ST_LSM6DSX_ID_ACC) return 0; - return hw->enable_event; + for (event = 0; event < ST_LSM6DSX_EVENT_MAX; event++) { + if (hw->enable_event[event]) + return true; + } + return false; } int st_lsm6dsx_sensor_set_enable(struct st_lsm6dsx_sensor *sensor, @@ -1873,9 +1878,10 @@ static int st_lsm6dsx_write_raw(struct iio_dev *iio_dev, return err; } -static int st_lsm6dsx_event_setup(struct st_lsm6dsx_hw *hw, bool state) +static int st_lsm6dsx_event_setup(struct st_lsm6dsx_hw *hw, + enum st_lsm6dsx_event_id event, int axis, + bool state) { - const struct st_lsm6dsx_reg *reg; const struct st_lsm6dsx_event_src *src; unsigned int data; int err; @@ -1883,22 +1889,24 @@ static int st_lsm6dsx_event_setup(struct st_lsm6dsx_hw *hw, bool state) if (!hw->irq_routing) return -ENOTSUPP; - reg = &hw->settings->event_settings.enable_reg; - if (reg->addr) { - data = ST_LSM6DSX_SHIFT_VAL(state, reg->mask); - err = st_lsm6dsx_update_bits_locked(hw, reg->addr, - reg->mask, data); - if (err < 0) - return err; - } - - /* Enable wakeup interrupt */ - src = &hw->settings->event_settings.sources[ST_LSM6DSX_EVENT_WAKEUP]; + /* Enable/disable event interrupt */ + src = &hw->settings->event_settings.sources[event]; data = ST_LSM6DSX_SHIFT_VAL(state, src->enable_mask); return st_lsm6dsx_update_bits_locked(hw, hw->irq_routing, src->enable_mask, data); } +static enum st_lsm6dsx_event_id +st_lsm6dsx_get_event_id(enum iio_event_type type) +{ + switch (type) { + case IIO_EV_TYPE_THRESH: + return ST_LSM6DSX_EVENT_WAKEUP; + default: + return ST_LSM6DSX_EVENT_MAX; + } +} + static int st_lsm6dsx_read_event(struct iio_dev *iio_dev, const struct iio_chan_spec *chan, enum iio_event_type type, @@ -1906,16 +1914,17 @@ static int st_lsm6dsx_read_event(struct iio_dev *iio_dev, enum iio_event_info info, int *val, int *val2) { + enum st_lsm6dsx_event_id event = st_lsm6dsx_get_event_id(type); struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); struct st_lsm6dsx_hw *hw = sensor->hw; const struct st_lsm6dsx_reg *reg; u8 data; int err; - if (type != IIO_EV_TYPE_THRESH) + if (event == ST_LSM6DSX_EVENT_MAX) return -EINVAL; - reg = &hw->settings->event_settings.sources[ST_LSM6DSX_EVENT_WAKEUP].value; + reg = &hw->settings->event_settings.sources[event].value; err = st_lsm6dsx_read_locked(hw, reg->addr, &data, sizeof(data)); if (err < 0) return err; @@ -1934,19 +1943,20 @@ st_lsm6dsx_write_event(struct iio_dev *iio_dev, enum iio_event_info info, int val, int val2) { + enum st_lsm6dsx_event_id event = st_lsm6dsx_get_event_id(type); struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); struct st_lsm6dsx_hw *hw = sensor->hw; const struct st_lsm6dsx_reg *reg; unsigned int data; int err; - if (type != IIO_EV_TYPE_THRESH) + if (event == ST_LSM6DSX_EVENT_MAX) return -EINVAL; if (val < 0 || val > 31) return -EINVAL; - reg = &hw->settings->event_settings.sources[ST_LSM6DSX_EVENT_WAKEUP].value; + reg = &hw->settings->event_settings.sources[event].value; data = ST_LSM6DSX_SHIFT_VAL(val, reg->mask); err = st_lsm6dsx_update_bits_locked(hw, reg->addr, reg->mask, data); @@ -1962,13 +1972,56 @@ st_lsm6dsx_read_event_config(struct iio_dev *iio_dev, enum iio_event_type type, enum iio_event_direction dir) { + enum st_lsm6dsx_event_id event = st_lsm6dsx_get_event_id(type); struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); struct st_lsm6dsx_hw *hw = sensor->hw; - if (type != IIO_EV_TYPE_THRESH) + if (event == ST_LSM6DSX_EVENT_MAX) return -EINVAL; - return !!(hw->enable_event & BIT(chan->channel2)); + return !!(hw->enable_event[event] & BIT(chan->channel2)); +} + +/** + * st_lsm6dsx_check_other_events - Check for enabled sensor events. + * @hw: Sensor hardware instance. + * @curr: Current event type. + * + * Return: whether any events other than @curr are enabled. + */ +static bool st_lsm6dsx_check_other_events(struct st_lsm6dsx_hw *hw, + enum st_lsm6dsx_event_id curr) +{ + enum st_lsm6dsx_event_id other; + + for (other = 0; other < ST_LSM6DSX_EVENT_MAX; other++) { + if (other != curr && hw->enable_event[other]) + return true; + } + + return false; +} + +static int st_lsm6dsx_events_enable(struct st_lsm6dsx_sensor *sensor, + bool state) +{ + struct st_lsm6dsx_hw *hw = sensor->hw; + const struct st_lsm6dsx_reg *reg; + + reg = &hw->settings->event_settings.enable_reg; + if (reg->addr) { + int err; + + err = regmap_update_bits(hw->regmap, reg->addr, reg->mask, + ST_LSM6DSX_SHIFT_VAL(state, reg->mask)); + if (err) + return err; + } + + if (state || !(hw->fifo_mask & BIT(sensor->id))) + return __st_lsm6dsx_sensor_set_enable(sensor, state); + + return 0; } static int @@ -1977,22 +2030,23 @@ st_lsm6dsx_write_event_config(struct iio_dev *iio_dev, enum iio_event_type type, enum iio_event_direction dir, bool state) { + enum st_lsm6dsx_event_id event = st_lsm6dsx_get_event_id(type); struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); struct st_lsm6dsx_hw *hw = sensor->hw; u8 enable_event; int err; - if (type != IIO_EV_TYPE_THRESH) + if (event == ST_LSM6DSX_EVENT_MAX) return -EINVAL; if (state) { - enable_event = hw->enable_event | BIT(chan->channel2); + enable_event = hw->enable_event[event] | BIT(chan->channel2); /* do not enable events if they are already enabled */ - if (hw->enable_event) + if (hw->enable_event[event]) goto out; } else { - enable_event = hw->enable_event & ~BIT(chan->channel2); + enable_event = hw->enable_event[event] & ~BIT(chan->channel2); /* only turn off sensor if no events is enabled */ if (enable_event) @@ -2000,22 +2054,24 @@ st_lsm6dsx_write_event_config(struct iio_dev *iio_dev, } /* stop here if no changes have been made */ - if (hw->enable_event == enable_event) + if (hw->enable_event[event] == enable_event) return 0; - err = st_lsm6dsx_event_setup(hw, state); + err = st_lsm6dsx_event_setup(hw, event, chan->channel2, state); if (err < 0) return err; mutex_lock(&hw->conf_lock); - if (enable_event || !(hw->fifo_mask & BIT(sensor->id))) - err = __st_lsm6dsx_sensor_set_enable(sensor, state); + if (enable_event) + err = st_lsm6dsx_events_enable(sensor, true); + else if (!st_lsm6dsx_check_other_events(hw, event)) + err = st_lsm6dsx_events_enable(sensor, false); mutex_unlock(&hw->conf_lock); if (err < 0) return err; out: - hw->enable_event = enable_event; + hw->enable_event[event] = enable_event; return 0; } @@ -2419,18 +2475,19 @@ static struct iio_dev *st_lsm6dsx_alloc_iiodev(struct st_lsm6dsx_hw *hw, } static bool -st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) +st_lsm6dsx_report_events(struct st_lsm6dsx_hw *hw, enum st_lsm6dsx_event_id id, + enum iio_event_type type, enum iio_event_direction dir) { const struct st_lsm6dsx_event_settings *event_settings; const struct st_lsm6dsx_event_src *src; int err, data; s64 timestamp; - if (!hw->enable_event) + if (!hw->enable_event[id]) return false; event_settings = &hw->settings->event_settings; - src = &event_settings->sources[ST_LSM6DSX_EVENT_WAKEUP]; + src = &event_settings->sources[id]; err = st_lsm6dsx_read_locked(hw, src->status.addr, &data, sizeof(data)); if (err < 0) @@ -2438,38 +2495,49 @@ st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) timestamp = iio_get_time_ns(hw->iio_devs[ST_LSM6DSX_ID_ACC]); if ((data & src->status_z_mask) && - (hw->enable_event & BIT(IIO_MOD_Z))) + (hw->enable_event[id] & BIT(IIO_MOD_Z))) iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_Z, - IIO_EV_TYPE_THRESH, - IIO_EV_DIR_EITHER), + type, + dir), timestamp); if ((data & src->status_y_mask) && - (hw->enable_event & BIT(IIO_MOD_Y))) + (hw->enable_event[id] & BIT(IIO_MOD_Y))) iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_Y, - IIO_EV_TYPE_THRESH, - IIO_EV_DIR_EITHER), + type, + dir), timestamp); if ((data & src->status_x_mask) && - (hw->enable_event & BIT(IIO_MOD_X))) + (hw->enable_event[id] & BIT(IIO_MOD_X))) iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X, - IIO_EV_TYPE_THRESH, - IIO_EV_DIR_EITHER), + type, + dir), timestamp); return data & src->status.mask; } +static bool st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) +{ + bool events_found; + + events_found = st_lsm6dsx_report_events(hw, ST_LSM6DSX_EVENT_WAKEUP, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER); + + return events_found; +} + static irqreturn_t st_lsm6dsx_handler_thread(int irq, void *private) { struct st_lsm6dsx_hw *hw = private; From 855119fa0a58a0aa1d40fe9c7c5da437167ba9ba Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Mon, 1 Dec 2025 11:00:16 +0100 Subject: [PATCH 014/297] iio: imu: st_lsm6dsx: add event configurability on a per axis basis In order to be able to configure event detection on a per axis basis (for either setting an event threshold/sensitivity value, or enabling/disabling event detection), add new axis-specific fields to struct st_lsm6dsx_event_src, and modify the logic that handles event configuration to properly handle axis-specific settings when supported by a given event source. A future commit will add actual event sources with per-axis configurability. Signed-off-by: Francesco Lavra Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 7 ++ drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 95 +++++++++++++++++--- 2 files changed, 88 insertions(+), 14 deletions(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index b5e8fc85e346..64c03a00456f 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -268,7 +268,14 @@ enum st_lsm6dsx_event_id { struct st_lsm6dsx_event_src { struct st_lsm6dsx_reg value; + struct st_lsm6dsx_reg x_value; + struct st_lsm6dsx_reg y_value; + struct st_lsm6dsx_reg z_value; u8 enable_mask; + u8 enable_axis_reg; + u8 enable_x_mask; + u8 enable_y_mask; + u8 enable_z_mask; struct st_lsm6dsx_reg status; u8 status_x_mask; u8 status_y_mask; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index 721abaf5315d..7f9f6ba52d48 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -1885,12 +1885,50 @@ static int st_lsm6dsx_event_setup(struct st_lsm6dsx_hw *hw, const struct st_lsm6dsx_event_src *src; unsigned int data; int err; + u8 old_enable, new_enable; if (!hw->irq_routing) return -ENOTSUPP; /* Enable/disable event interrupt */ src = &hw->settings->event_settings.sources[event]; + if (src->enable_axis_reg) { + u8 enable_mask; + + switch (axis) { + case IIO_MOD_X: + enable_mask = src->enable_x_mask; + break; + case IIO_MOD_Y: + enable_mask = src->enable_y_mask; + break; + case IIO_MOD_Z: + enable_mask = src->enable_z_mask; + break; + default: + enable_mask = 0; + } + if (enable_mask) { + data = ST_LSM6DSX_SHIFT_VAL(state, enable_mask); + err = st_lsm6dsx_update_bits_locked(hw, + src->enable_axis_reg, + enable_mask, data); + if (err < 0) + return err; + } + } + + /* + * If the set of axes for which the event source is enabled does not + * change from empty to non-empty or vice versa, there is nothing else + * to do. + */ + old_enable = hw->enable_event[event]; + new_enable = state ? (old_enable | BIT(axis)) : + (old_enable & ~BIT(axis)); + if (!old_enable == !new_enable) + return 0; + data = ST_LSM6DSX_SHIFT_VAL(state, src->enable_mask); return st_lsm6dsx_update_bits_locked(hw, hw->irq_routing, src->enable_mask, data); @@ -1907,6 +1945,39 @@ st_lsm6dsx_get_event_id(enum iio_event_type type) } } +static const struct st_lsm6dsx_reg * +st_lsm6dsx_get_event_reg(struct st_lsm6dsx_hw *hw, + enum st_lsm6dsx_event_id event, + const struct iio_chan_spec *chan) +{ + const struct st_lsm6dsx_event_src *src; + const struct st_lsm6dsx_reg *reg; + + src = &hw->settings->event_settings.sources[event]; + switch (chan->channel2) { + case IIO_MOD_X: + reg = &src->x_value; + break; + case IIO_MOD_Y: + reg = &src->y_value; + break; + case IIO_MOD_Z: + reg = &src->z_value; + break; + default: + return NULL; + } + if (reg->addr) + return reg; + + /* + * The sensor does not support configuring this event source on a per + * axis basis: return the register to configure the event source for all + * axes. + */ + return &src->value; +} + static int st_lsm6dsx_read_event(struct iio_dev *iio_dev, const struct iio_chan_spec *chan, enum iio_event_type type, @@ -1924,7 +1995,10 @@ static int st_lsm6dsx_read_event(struct iio_dev *iio_dev, if (event == ST_LSM6DSX_EVENT_MAX) return -EINVAL; - reg = &hw->settings->event_settings.sources[event].value; + reg = st_lsm6dsx_get_event_reg(hw, event, chan); + if (!reg) + return -EINVAL; + err = st_lsm6dsx_read_locked(hw, reg->addr, &data, sizeof(data)); if (err < 0) return err; @@ -1956,7 +2030,10 @@ st_lsm6dsx_write_event(struct iio_dev *iio_dev, if (val < 0 || val > 31) return -EINVAL; - reg = &hw->settings->event_settings.sources[event].value; + reg = st_lsm6dsx_get_event_reg(hw, event, chan); + if (!reg) + return -EINVAL; + data = ST_LSM6DSX_SHIFT_VAL(val, reg->mask); err = st_lsm6dsx_update_bits_locked(hw, reg->addr, reg->mask, data); @@ -2039,20 +2116,11 @@ st_lsm6dsx_write_event_config(struct iio_dev *iio_dev, if (event == ST_LSM6DSX_EVENT_MAX) return -EINVAL; - if (state) { + if (state) enable_event = hw->enable_event[event] | BIT(chan->channel2); - - /* do not enable events if they are already enabled */ - if (hw->enable_event[event]) - goto out; - } else { + else enable_event = hw->enable_event[event] & ~BIT(chan->channel2); - /* only turn off sensor if no events is enabled */ - if (enable_event) - goto out; - } - /* stop here if no changes have been made */ if (hw->enable_event[event] == enable_event) return 0; @@ -2070,7 +2138,6 @@ st_lsm6dsx_write_event_config(struct iio_dev *iio_dev, if (err < 0) return err; -out: hw->enable_event[event] = enable_event; return 0; From 317c9bef82ebe369fd1dac428eac505b5b6ff531 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Mon, 1 Dec 2025 11:00:17 +0100 Subject: [PATCH 015/297] iio: imu: st_lsm6dsx: add event spec parameter to iio_chan_spec initializer In preparation for adding support for more event sources, add to the ST_LSM6DSX_CHANNEL_ACC() iio_chan_spec initializer macro an iio_event_spec array argument, so that this macro can be used with different arrays by sensors that support different event sources; change the st_lsm6dsx_event struct declaration to an array (renamed as st_lsm6dsx_ev_motion) so that it can be passed to the macro (and opportunistically move it from the header file where it does not belong to the C file where it is used). In addition, remove from this macro the channel type parameter and hard-code IIO_ACCEL in the macro definition, since all callers use IIO_ACCEL as channel type argument. Signed-off-by: Francesco Lavra Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 15 ++++----------- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index 64c03a00456f..555b826185a4 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -81,9 +81,9 @@ enum st_lsm6dsx_hw_id { #define ST_LSM6DSX_SHIFT_VAL(val, mask) (((val) << __ffs(mask)) & (mask)) #define st_lsm6dsx_field_get(mask, reg) ((reg & mask) >> __ffs(mask)) -#define ST_LSM6DSX_CHANNEL_ACC(chan_type, addr, mod, scan_idx) \ +#define ST_LSM6DSX_CHANNEL_ACC(addr, mod, scan_idx, events) \ { \ - .type = chan_type, \ + .type = IIO_ACCEL, \ .address = addr, \ .modified = 1, \ .channel2 = mod, \ @@ -97,9 +97,9 @@ enum st_lsm6dsx_hw_id { .storagebits = 16, \ .endianness = IIO_LE, \ }, \ - .event_spec = &st_lsm6dsx_event, \ + .event_spec = events, \ + .num_event_specs = ARRAY_SIZE(events), \ .ext_info = st_lsm6dsx_ext_info, \ - .num_event_specs = 1, \ } #define ST_LSM6DSX_CHANNEL(chan_type, addr, mod, scan_idx) \ @@ -486,13 +486,6 @@ struct st_lsm6dsx_hw { } scan[ST_LSM6DSX_ID_MAX]; }; -static __maybe_unused const struct iio_event_spec st_lsm6dsx_event = { - .type = IIO_EV_TYPE_THRESH, - .dir = IIO_EV_DIR_EITHER, - .mask_separate = BIT(IIO_EV_INFO_VALUE) | - BIT(IIO_EV_INFO_ENABLE) -}; - static __maybe_unused const unsigned long st_lsm6dsx_available_scan_masks[] = { 0x7, 0x0, }; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index 7f9f6ba52d48..f28607a31f18 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -94,10 +94,19 @@ #define ST_LSM6DSX_REG_WHOAMI_ADDR 0x0f +static const struct iio_event_spec st_lsm6dsx_ev_motion[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + static const struct iio_chan_spec st_lsm6dsx_acc_channels[] = { - ST_LSM6DSX_CHANNEL_ACC(IIO_ACCEL, 0x28, IIO_MOD_X, 0), - ST_LSM6DSX_CHANNEL_ACC(IIO_ACCEL, 0x2a, IIO_MOD_Y, 1), - ST_LSM6DSX_CHANNEL_ACC(IIO_ACCEL, 0x2c, IIO_MOD_Z, 2), + ST_LSM6DSX_CHANNEL_ACC(0x28, IIO_MOD_X, 0, st_lsm6dsx_ev_motion), + ST_LSM6DSX_CHANNEL_ACC(0x2a, IIO_MOD_Y, 1, st_lsm6dsx_ev_motion), + ST_LSM6DSX_CHANNEL_ACC(0x2c, IIO_MOD_Z, 2, st_lsm6dsx_ev_motion), IIO_CHAN_SOFT_TIMESTAMP(3), }; From ce40e01d7ce2a15ba14fde314df30787fa1d58cb Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Mon, 1 Dec 2025 11:00:18 +0100 Subject: [PATCH 016/297] iio: imu: st_lsm6dsx: add tap event detection In order to allow sensors to advertise tap event capability and report tap events, define a new struct iio_event_spec array that includes a tap event spec, and a new struct iio_chan_spec array that references the new iio_event_spec array; for the LSM6DSV chip family, use the new iio_chan_spec array and define an event source for tap events. Tested on LSMDSV16X. Signed-off-by: Francesco Lavra Reviewed-by: Andy Shevchenko Acked-by: Lorenzo Bianconi Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 1 + drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 57 +++++++++++++++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index 555b826185a4..07b1773c87bd 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -263,6 +263,7 @@ struct st_lsm6dsx_shub_settings { enum st_lsm6dsx_event_id { ST_LSM6DSX_EVENT_WAKEUP, + ST_LSM6DSX_EVENT_TAP, ST_LSM6DSX_EVENT_MAX }; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index f28607a31f18..a39d25434015 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -103,6 +103,21 @@ static const struct iio_event_spec st_lsm6dsx_ev_motion[] = { }, }; +static const struct iio_event_spec st_lsm6dsx_ev_motion_tap[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_GESTURE, + .dir = IIO_EV_DIR_SINGLETAP, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + static const struct iio_chan_spec st_lsm6dsx_acc_channels[] = { ST_LSM6DSX_CHANNEL_ACC(0x28, IIO_MOD_X, 0, st_lsm6dsx_ev_motion), ST_LSM6DSX_CHANNEL_ACC(0x2a, IIO_MOD_Y, 1, st_lsm6dsx_ev_motion), @@ -110,6 +125,13 @@ static const struct iio_chan_spec st_lsm6dsx_acc_channels[] = { IIO_CHAN_SOFT_TIMESTAMP(3), }; +static const struct iio_chan_spec st_lsm6dsx_acc_tap_channels[] = { + ST_LSM6DSX_CHANNEL_ACC(0x28, IIO_MOD_X, 0, st_lsm6dsx_ev_motion_tap), + ST_LSM6DSX_CHANNEL_ACC(0x2a, IIO_MOD_Y, 1, st_lsm6dsx_ev_motion_tap), + ST_LSM6DSX_CHANNEL_ACC(0x2c, IIO_MOD_Z, 2, st_lsm6dsx_ev_motion_tap), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + static const struct iio_chan_spec st_lsm6dsx_gyro_channels[] = { ST_LSM6DSX_CHANNEL(IIO_ANGL_VEL, 0x22, IIO_MOD_X, 0), ST_LSM6DSX_CHANNEL(IIO_ANGL_VEL, 0x24, IIO_MOD_Y, 1), @@ -1255,8 +1277,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { }, .channels = { [ST_LSM6DSX_ID_ACC] = { - .chan = st_lsm6dsx_acc_channels, - .len = ARRAY_SIZE(st_lsm6dsx_acc_channels), + .chan = st_lsm6dsx_acc_tap_channels, + .len = ARRAY_SIZE(st_lsm6dsx_acc_tap_channels), }, [ST_LSM6DSX_ID_GYRO] = { .chan = st_lsm6dsx_gyro_channels, @@ -1435,6 +1457,32 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .status_y_mask = BIT(1), .status_x_mask = BIT(2), }, + [ST_LSM6DSX_EVENT_TAP] = { + .x_value = { + .addr = 0x57, + .mask = GENMASK(4, 0), + }, + .y_value = { + .addr = 0x58, + .mask = GENMASK(4, 0), + }, + .z_value = { + .addr = 0x59, + .mask = GENMASK(4, 0), + }, + .enable_mask = BIT(6), + .enable_axis_reg = 0x56, + .enable_x_mask = BIT(3), + .enable_y_mask = BIT(2), + .enable_z_mask = BIT(1), + .status = { + .addr = 0x46, + .mask = BIT(5), + }, + .status_x_mask = BIT(2), + .status_y_mask = BIT(1), + .status_z_mask = BIT(0), + }, }, }, }, @@ -1949,6 +1997,8 @@ st_lsm6dsx_get_event_id(enum iio_event_type type) switch (type) { case IIO_EV_TYPE_THRESH: return ST_LSM6DSX_EVENT_WAKEUP; + case IIO_EV_TYPE_GESTURE: + return ST_LSM6DSX_EVENT_TAP; default: return ST_LSM6DSX_EVENT_MAX; } @@ -2610,6 +2660,9 @@ static bool st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) events_found = st_lsm6dsx_report_events(hw, ST_LSM6DSX_EVENT_WAKEUP, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER); + events_found |= st_lsm6dsx_report_events(hw, ST_LSM6DSX_EVENT_TAP, + IIO_EV_TYPE_GESTURE, + IIO_EV_DIR_SINGLETAP); return events_found; } From 5a306a64bf790ed084601d7f3e89d5dc4a18a5c2 Mon Sep 17 00:00:00 2001 From: Tomas Borquez Date: Wed, 26 Nov 2025 17:32:40 -0300 Subject: [PATCH 017/297] iio: light: isl29018: replace sprintf() with safer alternatives This patch replaces sprintf() with sysfs_emit() and sysfs_emit_at() safer alternative with no functional changes. Signed-off-by: Tomas Borquez Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/light/isl29018.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/iio/light/isl29018.c b/drivers/iio/light/isl29018.c index 1b4c18423048..b6ab726d1dae 100644 --- a/drivers/iio/light/isl29018.c +++ b/drivers/iio/light/isl29018.c @@ -273,9 +273,9 @@ static ssize_t in_illuminance_scale_available_show mutex_lock(&chip->lock); for (i = 0; i < ARRAY_SIZE(isl29018_scales[chip->int_time]); ++i) - len += sprintf(buf + len, "%d.%06d ", - isl29018_scales[chip->int_time][i].scale, - isl29018_scales[chip->int_time][i].uscale); + len += sysfs_emit_at(buf, len, "%d.%06d ", + isl29018_scales[chip->int_time][i].scale, + isl29018_scales[chip->int_time][i].uscale); mutex_unlock(&chip->lock); buf[len - 1] = '\n'; @@ -293,8 +293,8 @@ static ssize_t in_illuminance_integration_time_available_show int len = 0; for (i = 0; i < ARRAY_SIZE(isl29018_int_utimes[chip->type]); ++i) - len += sprintf(buf + len, "0.%06d ", - isl29018_int_utimes[chip->type][i]); + len += sysfs_emit_at(buf, len, "0.%06d ", + isl29018_int_utimes[chip->type][i]); buf[len - 1] = '\n'; @@ -330,7 +330,7 @@ static ssize_t proximity_on_chip_ambient_infrared_suppression_show * Return the "proximity scheme" i.e. if the chip does on chip * infrared suppression (1 means perform on chip suppression) */ - return sprintf(buf, "%d\n", chip->prox_scheme); + return sysfs_emit(buf, "%d\n", chip->prox_scheme); } static ssize_t proximity_on_chip_ambient_infrared_suppression_store From 3624f038629d6b9ddf742f419ed7437bba0d2262 Mon Sep 17 00:00:00 2001 From: Massimiliano Pellizzer Date: Tue, 25 Nov 2025 18:18:16 +0100 Subject: [PATCH 018/297] iio: imu: smi330: remove redundant assignment in smi330_read_avail In the IIO_CHAN_INFO_OVERSAMPLING_RATIO case, the type parameter is assigned from smi330_average_attr.type and then immediately overwritten with IIO_VAL_INT on the next line. Since smi330_average_attr.type is already initialized to IIO_VAL_INT, the second assignment is redundant. Remove the hardcoded assignment to maintain consistency in the code. Signed-off-by: Massimiliano Pellizzer Reviewed-by: Jianping Shen Signed-off-by: Jonathan Cameron --- drivers/iio/imu/smi330/smi330_core.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/iio/imu/smi330/smi330_core.c b/drivers/iio/imu/smi330/smi330_core.c index 7564f12543e0..0cf673b44b62 100644 --- a/drivers/iio/imu/smi330/smi330_core.c +++ b/drivers/iio/imu/smi330/smi330_core.c @@ -475,7 +475,6 @@ static int smi330_read_avail(struct iio_dev *indio_dev, *vals = smi330_average_attr.vals; *length = smi330_average_attr.len; *type = smi330_average_attr.type; - *type = IIO_VAL_INT; return IIO_AVAIL_LIST; case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: *vals = smi330_bandwidth_attr.vals; From 1ca733e843ac1340c030b4a8b0060a27cd0843b6 Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Tue, 18 Nov 2025 15:18:20 +0100 Subject: [PATCH 019/297] bindings: iio: adc: Add bindings for TI ADS131M0x ADCs Add device tree bindings documentation for the Texas Instruments ADS131M0x analog-to-digital converters. This family includes the ADS131M02, ADS131M03, ADS131M04, ADS131M06, and ADS131M08 variants. These variants differ primarily in the number of supported channels (2, 3, 4, 6, and 8, respectively), which requires separate compatible strings to validate the channel nodes. Signed-off-by: Oleksij Rempel Reviewed-by: Conor Dooley Reviewed-by: David Lechner Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/ti,ads131m02.yaml | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads131m02.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads131m02.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads131m02.yaml new file mode 100644 index 000000000000..5d52bb7dd5d4 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads131m02.yaml @@ -0,0 +1,208 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/ti,ads131m02.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments ADS131M0x 2-, 3-, 4-, 6- and 8-Channel ADCs + +maintainers: + - Oleksij Rempel + +description: | + The ADS131M0x are a family of multichannel, simultaneous sampling, + 24-bit, delta-sigma, analog-to-digital converters (ADCs) with a + built-in programmable gain amplifier (PGA) and internal reference. + Communication with the ADC chip is via SPI. + + Datasheets: + - ADS131M02: https://www.ti.com/lit/ds/symlink/ads131m02.pdf + - ADS131M03: https://www.ti.com/lit/ds/symlink/ads131m03.pdf + - ADS131M04: https://www.ti.com/lit/ds/symlink/ads131m04.pdf + - ADS131M06: https://www.ti.com/lit/ds/symlink/ads131m06.pdf + - ADS131M08: https://www.ti.com/lit/ds/symlink/ads131m08.pdf + +properties: + compatible: + enum: + - ti,ads131m02 + - ti,ads131m03 + - ti,ads131m04 + - ti,ads131m06 + - ti,ads131m08 + + reg: + description: SPI chip select number. + + clocks: + description: + Phandle to the external clock source required by the ADC's CLKIN pin. + The datasheet recommends specific frequencies based on the desired power + mode (e.g., 8.192 MHz for High-Resolution mode). + maxItems: 1 + + avdd-supply: + description: Analog power supply (AVDD). + + dvdd-supply: + description: Digital power supply (DVDD). + + interrupts: + description: DRDY (Data Ready) output signal. + maxItems: 1 + + reset-gpios: + description: Optional RESET signal. + maxItems: 1 + + clock-names: + description: + Indicates if a crystal oscillator (XTAL) or CMOS signal is connected + (CLKIN). Note that XTAL mode is only supported on ADS131M06 and ADS131M08. + enum: [xtal, clkin] + + refin-supply: + description: Optional external reference supply (REFIN). + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +required: + - compatible + - reg + - clocks + - clock-names + - avdd-supply + - dvdd-supply + +patternProperties: + "^channel@[0-7]$": + type: object + $ref: /schemas/iio/adc/adc.yaml# + description: Properties for a single ADC channel. + + properties: + reg: + description: The channel index (0-7). + minimum: 0 + maximum: 7 # Max channels on ADS131M08 + + label: true + + required: + - reg + + unevaluatedProperties: false + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + + - if: + # 20-pin devices: M02, M03, M04 + # These do not support XTAL or REFIN. + properties: + compatible: + enum: + - ti,ads131m02 + - ti,ads131m03 + - ti,ads131m04 + then: + properties: + clock-names: + const: clkin + refin-supply: false + + - if: + # ADS131M02: 2 channels max (0-1) + properties: + compatible: + contains: + const: ti,ads131m02 + then: + patternProperties: + "^channel@[0-1]$": + properties: + reg: + maximum: 1 + "^channel@[2-7]$": false + + - if: + # ADS131M03: 3 channels max (0-2) + properties: + compatible: + contains: + const: ti,ads131m03 + then: + patternProperties: + "^channel@[0-2]$": + properties: + reg: + maximum: 2 + "^channel@[3-7]$": false + + - if: + # ADS131M04: 4 channels max (0-3) + properties: + compatible: + contains: + const: ti,ads131m04 + then: + patternProperties: + "^channel@[0-3]$": + properties: + reg: + maximum: 3 + "^channel@[4-7]$": false + + - if: + # ADS131M06: 6 channels max (0-5) + properties: + compatible: + contains: + const: ti,ads131m06 + then: + patternProperties: + "^channel@[0-5]$": + properties: + reg: + maximum: 5 + "^channel@[6-7]$": false + +unevaluatedProperties: false + +examples: + - | + #include + + spi1 { + #address-cells = <1>; + #size-cells = <0>; + + adc@0 { + compatible = "ti,ads131m02"; + reg = <0>; + spi-max-frequency = <8000000>; + + clocks = <&rcc CK_MCO2>; + clock-names = "clkin"; + + avdd-supply = <&vdd_ana>; + dvdd-supply = <&vdd_dig>; + + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + label = "input_voltage"; + }; + + channel@1 { + reg = <1>; + label = "input_current"; + }; + }; + }; From 4aa91223fd6c9b9e6c73f9dc6ffb55cbf04df4ac Mon Sep 17 00:00:00 2001 From: David Jander Date: Tue, 18 Nov 2025 15:18:21 +0100 Subject: [PATCH 020/297] iio: adc: Add TI ADS131M0x ADC driver Add a new IIO ADC driver for Texas Instruments ADS131M0x devices (ADS131M02/03/04/06/08). These are 24-bit, up to 64 kSPS, simultaneous- sampling delta-sigma ADCs accessed via SPI. Highlights: - Supports 2/3/4/6/8-channel variants with per-channel RAW and SCALE. - Implements device-required full-duplex fixed-frame transfers. - Handles both input and output CRC Note: Despite the almost identical name, this hardware is not compatible with the ADS131E0x series handled by drivers/iio/adc/ti-ads131e08.c. Signed-off-by: David Jander Co-developed-by: Oleksij Rempel Signed-off-by: Oleksij Rempel Reviewed-by: David Lechner Signed-off-by: Jonathan Cameron --- drivers/iio/adc/Kconfig | 11 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ti-ads131m02.c | 968 +++++++++++++++++++++++++++++++++ 3 files changed, 980 insertions(+) create mode 100644 drivers/iio/adc/ti-ads131m02.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 58da8255525e..9c4c1e23090a 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1722,6 +1722,17 @@ config TI_ADS131E08 This driver can also be built as a module. If so, the module will be called ti-ads131e08. +config TI_ADS131M02 + tristate "Texas Instruments ADS131M02" + depends on SPI && REGULATOR + select CRC_ITU_T + help + Say yes here to get support for Texas Instruments ADS131M02, ADS131M03, + ADS131M04, ADS131M06 and ADS131M08 chips. + + This driver can also be built as a module. If so, the module will be + called ti-ads131m02. + config TI_ADS7138 tristate "Texas Instruments ADS7128 and ADS7138 ADC driver" depends on I2C diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 7cc8f9a12f76..ca3f87510331 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -150,6 +150,7 @@ obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o obj-$(CONFIG_TI_ADS1298) += ti-ads1298.o obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o +obj-$(CONFIG_TI_ADS131M02) += ti-ads131m02.o obj-$(CONFIG_TI_ADS7138) += ti-ads7138.o obj-$(CONFIG_TI_ADS7924) += ti-ads7924.o obj-$(CONFIG_TI_ADS7950) += ti-ads7950.o diff --git a/drivers/iio/adc/ti-ads131m02.c b/drivers/iio/adc/ti-ads131m02.c new file mode 100644 index 000000000000..07d63bf62c5f --- /dev/null +++ b/drivers/iio/adc/ti-ads131m02.c @@ -0,0 +1,968 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Texas Instruments ADS131M02 family ADC chips. + * + * Copyright (C) 2024 Protonic Holland + * Copyright (C) 2025 Oleksij Rempel , Pengutronix + * + * Primary Datasheet Reference (used for citations): + * ADS131M08 8-Channel, Simultaneously-Sampling, 24-Bit, Delta-Sigma ADC + * Document SBAS950B, Revised February 2021 + * https://www.ti.com/lit/ds/symlink/ads131m08.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Max channels supported by the largest variant in the family (ADS131M08) */ +#define ADS131M_MAX_CHANNELS 8 + +/* Section 6.7, t_REGACQ (min time after reset) is 5us */ +#define ADS131M_RESET_DELAY_US 5 + +#define ADS131M_WORD_SIZE_BYTES 3 +#define ADS131M_RESPONSE_WORDS 1 +#define ADS131M_CRC_WORDS 1 + +/* + * SPI Frame word count calculation. + * Frame = N channel words + 1 response word + 1 CRC word. + * Word size depends on WLENGTH bits in MODE register (Default 24-bit). + */ +#define ADS131M_FRAME_WORDS(nch) \ + ((nch) + ADS131M_RESPONSE_WORDS + ADS131M_CRC_WORDS) + +/* + * SPI Frame byte size calculation. + * Assumes default word size of 24 bits (3 bytes). + */ +#define ADS131M_FRAME_BYTES(nch) \ + (ADS131M_FRAME_WORDS(nch) * ADS131M_WORD_SIZE_BYTES) + +/* + * Index calculation for the start byte of channel 'x' data within the RX buffer. + * Assumes 24-bit words (3 bytes per word). + * The received frame starts with the response word (e.g., STATUS register + * content when NULL command was sent), followed by data for channels 0 to N-1, + * and finally the output CRC word. + * Response = index 0..2, Chan0 = index 3..5, Chan1 = index 6..8, ... + * Index for ChanX = 3 (response) + x * 3 (channel data size). + */ +#define ADS131M_CHANNEL_INDEX(x) \ + ((x) * ADS131M_WORD_SIZE_BYTES + ADS131M_WORD_SIZE_BYTES) + +#define ADS131M_CMD_NULL 0x0000 +#define ADS131M_CMD_RESET 0x0011 + +#define ADS131M_CMD_ADDR_MASK GENMASK(11, 7) +#define ADS131M_CMD_NUM_MASK GENMASK(6, 0) + +#define ADS131M_CMD_RREG_OP 0xa000 +#define ADS131M_CMD_WREG_OP 0x6000 + +#define ADS131M_CMD_RREG(a, n) \ + (ADS131M_CMD_RREG_OP | \ + FIELD_PREP(ADS131M_CMD_ADDR_MASK, a) | \ + FIELD_PREP(ADS131M_CMD_NUM_MASK, n)) +#define ADS131M_CMD_WREG(a, n) \ + (ADS131M_CMD_WREG_OP | \ + FIELD_PREP(ADS131M_CMD_ADDR_MASK, a) | \ + FIELD_PREP(ADS131M_CMD_NUM_MASK, n)) + +/* STATUS Register (0x01h) bit definitions */ +#define ADS131M_STATUS_CRC_ERR BIT(12) /* Input CRC error */ + +#define ADS131M_REG_MODE 0x02 +#define ADS131M_MODE_RX_CRC_EN BIT(12) /* Enable Input CRC */ +#define ADS131M_MODE_CRC_TYPE_ANSI BIT(11) /* 0 = CCITT, 1 = ANSI */ +#define ADS131M_MODE_RESET_FLAG BIT(10) + +#define ADS131M_REG_CLOCK 0x03 +#define ADS131M_CLOCK_XTAL_DIS BIT(7) +#define ADS131M_CLOCK_EXTREF_EN BIT(6) + +/* 1.2V internal reference, in millivolts, for IIO_VAL_FRACTIONAL_LOG2 */ +#define ADS131M_VREF_INTERNAL_mV 1200 +/* 24-bit resolution */ +#define ADS131M_RESOLUTION_BITS 24 +/* Signed data uses (RESOLUTION_BITS - 1) magnitude bits */ +#define ADS131M_CODE_BITS (ADS131M_RESOLUTION_BITS - 1) + +/* External ref FSR = Vref * 0.96 */ +#define ADS131M_EXTREF_SCALE_NUM 96 +#define ADS131M_EXTREF_SCALE_DEN 100 + +struct ads131m_configuration { + const struct iio_chan_spec *channels; + const char *name; + u16 reset_ack; + u8 num_channels; + u8 supports_extref:1; + u8 supports_xtal:1; +}; + +struct ads131m_priv { + struct iio_dev *indio_dev; + struct spi_device *spi; + const struct ads131m_configuration *config; + + bool use_external_ref; + int scale_val; + int scale_val2; + + struct spi_transfer xfer; + struct spi_message msg; + + /* + * Protects the shared tx_buffer and rx_buffer. More importantly, + * this serializes all SPI communication to ensure the atomicity + * of multi-cycle command sequences (like WREG, RREG, or RESET). + */ + struct mutex lock; + + /* DMA-safe buffers should be placed at the end of the struct. */ + u8 tx_buffer[ADS131M_FRAME_BYTES(ADS131M_MAX_CHANNELS)] + __aligned(IIO_DMA_MINALIGN); + u8 rx_buffer[ADS131M_FRAME_BYTES(ADS131M_MAX_CHANNELS)]; +}; + +/** + * ads131m_tx_frame_unlocked - Sends a command frame with Input CRC + * @priv: Device private data structure. + * @command: The 16-bit command to send (e.g., NULL, RREG, RESET). + * + * This function sends a command in Word 0, and its calculated 16-bit + * CRC in Word 1, as required when Input CRC is enabled. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_tx_frame_unlocked(struct ads131m_priv *priv, u32 command) +{ + struct iio_dev *indio_dev = priv->indio_dev; + u16 crc; + + lockdep_assert_held(&priv->lock); + + memset(priv->tx_buffer, 0, ADS131M_FRAME_BYTES(indio_dev->num_channels)); + + /* Word 0: 16-bit command, MSB-aligned in 24-bit word */ + put_unaligned_be16(command, &priv->tx_buffer[0]); + + /* Word 1: Input CRC. Calculated over the 3 bytes of Word 0. */ + crc = crc_itu_t(0xffff, priv->tx_buffer, 3); + put_unaligned_be16(crc, &priv->tx_buffer[3]); + + return spi_sync(priv->spi, &priv->msg); +} + +/** + * ads131m_rx_frame_unlocked - Receives a full SPI data frame. + * @priv: Device private data structure. + * + * This function sends a NULL command (with its CRC) to clock out a + * full SPI frame from the device (e.g., response + channel data + CRC). + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_rx_frame_unlocked(struct ads131m_priv *priv) +{ + return ads131m_tx_frame_unlocked(priv, ADS131M_CMD_NULL); +} + +/** + * ads131m_check_status_crc_err - Checks for an Input CRC error. + * @priv: Device private data structure. + * + * Sends a NULL command to fetch the STATUS register and checks the + * CRC_ERR bit. This is used to verify the integrity of the previous + * command (like RREG or WREG). + * + * Return: 0 on success, -EIO if CRC_ERR bit is set. + */ +static int ads131m_check_status_crc_err(struct ads131m_priv *priv) +{ + struct device *dev = &priv->spi->dev; + u16 status; + int ret; + + lockdep_assert_held(&priv->lock); + + ret = ads131m_rx_frame_unlocked(priv); + if (ret < 0) { + dev_err_ratelimited(dev, + "SPI error on STATUS read for CRC check\n"); + return ret; + } + + status = get_unaligned_be16(&priv->rx_buffer[0]); + if (status & ADS131M_STATUS_CRC_ERR) { + dev_err_ratelimited(dev, + "Input CRC error reported in STATUS = 0x%04x\n", + status); + return -EIO; + } + + return 0; +} + +/** + * ads131m_write_reg_unlocked - Writes a single register and verifies the ACK. + * @priv: Device private data structure. + * @reg: The 8-bit register address. + * @val: The 16-bit value to write. + * + * This function performs the full 3-cycle WREG operation with Input CRC: + * 1. (Cycle 1) Sends WREG command, data, and its calculated CRC. + * 2. (Cycle 2) Sends NULL+CRC to retrieve the response from Cycle 1. + * 3. Verifies the response is the correct ACK for the WREG. + * 4. (Cycle 3) Sends NULL+CRC to retrieve STATUS and check for CRC_ERR. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_write_reg_unlocked(struct ads131m_priv *priv, u8 reg, u16 val) +{ + struct iio_dev *indio_dev = priv->indio_dev; + u16 command, expected_ack, response, crc; + struct device *dev = &priv->spi->dev; + int ret_crc_err = 0; + int ret; + + lockdep_assert_held(&priv->lock); + + command = ADS131M_CMD_WREG(reg, 0); /* n = 0 for 1 register */ + /* + * Per Table 8-11, WREG response is: 010a aaaa ammm mmmm + * For 1 reg (n = 0 -> m = 0): 010a aaaa a000 0000 = 0x4000 | (reg << 7) + */ + expected_ack = 0x4000 | (reg << 7); + + /* Cycle 1: Send WREG Command + Data + Input CRC */ + + memset(priv->tx_buffer, 0, ADS131M_FRAME_BYTES(indio_dev->num_channels)); + + /* Word 0: WREG command, 1 reg (n = 0), MSB-aligned */ + put_unaligned_be16(command, &priv->tx_buffer[0]); + + /* Word 1: Data, MSB-aligned */ + put_unaligned_be16(val, &priv->tx_buffer[3]); + + /* Word 2: Input CRC. Calculated over Word 0 (Cmd) and Word 1 (Data). */ + crc = crc_itu_t(0xffff, priv->tx_buffer, 6); + put_unaligned_be16(crc, &priv->tx_buffer[6]); + + /* Ignore the RX buffer (it's from the previous command) */ + ret = spi_sync(priv->spi, &priv->msg); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI error on WREG (cycle 1)\n"); + return ret; + } + + /* Cycle 2: Send NULL Command to get the WREG response */ + ret = ads131m_rx_frame_unlocked(priv); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI error on WREG ACK (cycle 2)\n"); + return ret; + } + + /* + * Response is in the first 2 bytes of the RX buffer + * (MSB-aligned 16-bit response) + */ + response = get_unaligned_be16(&priv->rx_buffer[0]); + if (response != expected_ack) { + dev_err_ratelimited(dev, "WREG(0x%02x) failed, expected ACK 0x%04x, got 0x%04x\n", + reg, expected_ack, response); + ret_crc_err = -EIO; + /* + * Don't return yet, still need to do Cycle 3 to clear + * any potential CRC_ERR flag from this failed command. + */ + } + + /* + * Cycle 3: Check STATUS for Input CRC error. + * This is necessary even if ACK was wrong, to clear the CRC_ERR flag. + */ + ret = ads131m_check_status_crc_err(priv); + if (ret < 0) + return ret; + + return ret_crc_err; +} + +/** + * ads131m_read_reg_unlocked - Reads a single register from the device. + * @priv: Device private data structure. + * @reg: The 8-bit register address. + * @val: Pointer to store the 16-bit register value. + * + * This function performs the full 3-cycle RREG operation with Input CRC: + * 1. (Cycle 1) Sends the RREG command + Input CRC. + * 2. (Cycle 2) Sends NULL+CRC to retrieve the register data. + * 3. (Cycle 3) Sends NULL+CRC to retrieve STATUS and check for CRC_ERR. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_read_reg_unlocked(struct ads131m_priv *priv, u8 reg, u16 *val) +{ + struct device *dev = &priv->spi->dev; + u16 command; + int ret; + + lockdep_assert_held(&priv->lock); + + command = ADS131M_CMD_RREG(reg, 0); /* n=0 for 1 register */ + + /* + * Cycle 1: Send RREG Command + Input CRC + * Ignore the RX buffer (it's from the previous command) + */ + ret = ads131m_tx_frame_unlocked(priv, command); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI error on RREG (cycle 1)\n"); + return ret; + } + + /* Cycle 2: Send NULL Command to get the register data */ + ret = ads131m_rx_frame_unlocked(priv); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI error on RREG data (cycle 2)\n"); + return ret; + } + + /* + * Per datasheet, for a single reg read, the response is the data. + * It's in the first 2 bytes of the RX buffer (MSB-aligned 16-bit). + */ + *val = get_unaligned_be16(&priv->rx_buffer[0]); + + /* + * Cycle 3: Check STATUS for Input CRC error. + * The RREG command does not execute if CRC is bad, but we read + * STATUS anyway to clear the flag in case it was set. + */ + return ads131m_check_status_crc_err(priv); +} + +/** + * ads131m_rmw_reg - Reads, modifies, and writes a single register. + * @priv: Device private data structure. + * @reg: The 8-bit register address. + * @clear: Bitmask of bits to clear. + * @set: Bitmask of bits to set. + * + * This function performs an atomic read-modify-write operation on a register. + * It reads the register, applies the clear and set masks, and writes + * the new value back if it has changed. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_rmw_reg(struct ads131m_priv *priv, u8 reg, u16 clear, u16 set) +{ + u16 old_val, new_val; + int ret; + + guard(mutex)(&priv->lock); + + ret = ads131m_read_reg_unlocked(priv, reg, &old_val); + if (ret < 0) + return ret; + + new_val = (old_val & ~clear) | set; + if (new_val == old_val) + return 0; + + return ads131m_write_reg_unlocked(priv, reg, new_val); +} + +/** + * ads131m_verify_output_crc - Verifies the CRC of the received SPI frame. + * @priv: Device private data structure. + * + * This function calculates the CRC-16-CCITT (Poly 0x1021, Seed 0xFFFF) over + * the received response and channel data, and compares it to the CRC word + * received at the end of the SPI frame. + * + * Return: 0 on success, -EIO on CRC mismatch. + */ +static int ads131m_verify_output_crc(struct ads131m_priv *priv) +{ + struct iio_dev *indio_dev = priv->indio_dev; + struct device *dev = &priv->spi->dev; + u16 calculated_crc, received_crc; + size_t data_len; + + lockdep_assert_held(&priv->lock); + + /* + * Frame: [Response][Chan 0]...[Chan N-1][CRC Word] + * Data for CRC: [Response][Chan 0]...[Chan N-1] + * Data length = (N_channels + 1) * 3 bytes (at 24-bit word size) + */ + data_len = ADS131M_FRAME_BYTES(indio_dev->num_channels) - 3; + calculated_crc = crc_itu_t(0xffff, priv->rx_buffer, data_len); + + /* + * The received 16-bit CRC is MSB-aligned in the last 24-bit word. + * We extract it from the first 2 bytes (BE) of that word. + */ + received_crc = get_unaligned_be16(&priv->rx_buffer[data_len]); + if (calculated_crc != received_crc) { + dev_err_ratelimited(dev, "Output CRC error. Got %04x, expected %04x\n", + received_crc, calculated_crc); + return -EIO; + } + + return 0; +} + +/** + * ads131m_adc_read - Reads channel data, checks input and output CRCs. + * @priv: Device private data structure. + * @channel: The channel number to read. + * @val: Pointer to store the raw 24-bit value. + * + * This function sends a NULL command (with Input CRC) to retrieve data. + * It checks the received STATUS word for any Input CRC errors from the + * previous command, and then verifies the Output CRC of the current + * data frame. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_adc_read(struct ads131m_priv *priv, u8 channel, s32 *val) +{ + struct device *dev = &priv->spi->dev; + u16 status; + int ret; + u8 *buf; + + guard(mutex)(&priv->lock); + + /* Send NULL command + Input CRC, and receive data frame */ + ret = ads131m_rx_frame_unlocked(priv); + if (ret < 0) + return ret; + + /* + * Check STATUS for Input CRC error from the previous command frame. + * Note: the STATUS word belongs to the frame before this NULL command. + */ + status = get_unaligned_be16(&priv->rx_buffer[0]); + if (status & ADS131M_STATUS_CRC_ERR) { + dev_err_ratelimited(dev, + "Previous input CRC error reported in STATUS (0x%04x)\n", + status); + } + + ret = ads131m_verify_output_crc(priv); + if (ret < 0) + return ret; + + buf = &priv->rx_buffer[ADS131M_CHANNEL_INDEX(channel)]; + *val = sign_extend32(get_unaligned_be24(buf), ADS131M_CODE_BITS); + + return 0; +} + +static int ads131m_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *channel, + int *val, int *val2, long mask) +{ + struct ads131m_priv *priv = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = ads131m_adc_read(priv, channel->channel, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = priv->scale_val; + *val2 = priv->scale_val2; + + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +#define ADS131M_VOLTAGE_CHANNEL(num) \ + { \ + .type = IIO_VOLTAGE, \ + .differential = 1, \ + .indexed = 1, \ + .channel = (num), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + } + +static const struct iio_chan_spec ads131m02_channels[] = { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), +}; + +static const struct iio_chan_spec ads131m03_channels[] = { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), +}; + +static const struct iio_chan_spec ads131m04_channels[] = { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), + ADS131M_VOLTAGE_CHANNEL(3), +}; + +static const struct iio_chan_spec ads131m06_channels[] = { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), + ADS131M_VOLTAGE_CHANNEL(3), + ADS131M_VOLTAGE_CHANNEL(4), + ADS131M_VOLTAGE_CHANNEL(5), +}; + +static const struct iio_chan_spec ads131m08_channels[] = { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), + ADS131M_VOLTAGE_CHANNEL(3), + ADS131M_VOLTAGE_CHANNEL(4), + ADS131M_VOLTAGE_CHANNEL(5), + ADS131M_VOLTAGE_CHANNEL(6), + ADS131M_VOLTAGE_CHANNEL(7), +}; + +static const struct ads131m_configuration ads131m02_config = { + .channels = ads131m02_channels, + .num_channels = ARRAY_SIZE(ads131m02_channels), + .reset_ack = 0xff22, + .name = "ads131m02", +}; + +static const struct ads131m_configuration ads131m03_config = { + .channels = ads131m03_channels, + .num_channels = ARRAY_SIZE(ads131m03_channels), + .reset_ack = 0xff23, + .name = "ads131m03", +}; + +static const struct ads131m_configuration ads131m04_config = { + .channels = ads131m04_channels, + .num_channels = ARRAY_SIZE(ads131m04_channels), + .reset_ack = 0xff24, + .name = "ads131m04", +}; + +static const struct ads131m_configuration ads131m06_config = { + .channels = ads131m06_channels, + .num_channels = ARRAY_SIZE(ads131m06_channels), + .reset_ack = 0xff26, + .supports_extref = true, + .supports_xtal = true, + .name = "ads131m06", +}; + +static const struct ads131m_configuration ads131m08_config = { + .channels = ads131m08_channels, + .num_channels = ARRAY_SIZE(ads131m08_channels), + .reset_ack = 0xff28, + .supports_extref = true, + .supports_xtal = true, + .name = "ads131m08", +}; + +static const struct iio_info ads131m_info = { + .read_raw = ads131m_read_raw, +}; + +/* + * Prepares the reusable SPI message structure for a full-duplex transfer. + * The ADS131M requires sending a command frame while simultaneously + * receiving the response/data frame from the previous command cycle. + * + * This message is optimized for the primary data acquisition workflow: + * sending a single-word command (like NULL) and receiving a full data + * frame (Response + N*Channels + CRC). + * + * This message is sized for a full data frame and is reused for all + * command/data cycles. The driver does not implement variable-length SPI + * messages. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_prepare_message(struct ads131m_priv *priv) +{ + struct iio_dev *indio_dev = priv->indio_dev; + struct device *dev = &priv->spi->dev; + int ret; + + priv->xfer.tx_buf = priv->tx_buffer; + priv->xfer.rx_buf = priv->rx_buffer; + priv->xfer.len = ADS131M_FRAME_BYTES(indio_dev->num_channels); + spi_message_init_with_transfers(&priv->msg, &priv->xfer, 1); + + ret = devm_spi_optimize_message(dev, priv->spi, &priv->msg); + if (ret) + return dev_err_probe(dev, ret, "failed to optimize SPI message\n"); + + return 0; +} + +/** + * ads131m_hw_reset - Pulses the optional hardware reset. + * @priv: Device private data structure. + * @rstc: Reset control for the /RESET line. + * + * Pulses the /RESET line to perform a hardware reset and waits the + * required t_REGACQ time for the device to be ready. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_hw_reset(struct ads131m_priv *priv, + struct reset_control *rstc) +{ + struct device *dev = &priv->spi->dev; + int ret; + + /* + * Manually pulse the reset line using the framework. + * The reset-gpio provider does not implement the .reset op, + * so we must use .assert and .deassert. + */ + ret = reset_control_assert(rstc); + if (ret) + return dev_err_probe(dev, ret, "Failed to assert reset\n"); + + /* Datasheet: Hold /RESET low for > 2 f_CLKIN cycles. 1us is ample. */ + fsleep(1); + + ret = reset_control_deassert(rstc); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to deassert reset\n"); + + /* Wait t_REGACQ (5us) for registers to be accessible */ + fsleep(ADS131M_RESET_DELAY_US); + + return 0; +} + +/** + * ads131m_sw_reset - Issues a software RESET and verifies ACK. + * @priv: Device private data structure. + * + * This function sends a RESET command (with Input CRC), waits t_REGACQ, + * reads back the RESET ACK, and then sends a final NULL to check for + * any input CRC errors. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_sw_reset(struct ads131m_priv *priv) +{ + u16 expected_ack = priv->config->reset_ack; + struct device *dev = &priv->spi->dev; + u16 response; + int ret; + + guard(mutex)(&priv->lock); + + ret = ads131m_tx_frame_unlocked(priv, ADS131M_CMD_RESET); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to send RESET command\n"); + + /* Wait t_REGACQ (5us) for device to be ready after reset */ + fsleep(ADS131M_RESET_DELAY_US); + + /* Cycle 2: Send NULL + CRC to retrieve the response to the RESET */ + ret = ads131m_rx_frame_unlocked(priv); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to read RESET ACK\n"); + + response = get_unaligned_be16(&priv->rx_buffer[0]); + + /* Check against the device-specific ACK value */ + if (response != expected_ack) + return dev_err_probe(dev, -EIO, + "RESET ACK mismatch, got 0x%04x, expected 0x%04x\n", + response, expected_ack); + + /* Cycle 3: Check STATUS for Input CRC error on the RESET command. */ + return ads131m_check_status_crc_err(priv); +} + +/** + * ads131m_reset - Resets the device using hardware or software. + * @priv: Device private data structure. + * @rstc: Optional reset control, or NULL for software reset. + * + * This function performs a hardware reset if supported (rstc provided), + * otherwise it issues a software RESET command via SPI. + * + * Note: The software reset path also validates the device's reset + * acknowledgment against the expected ID for the compatible string. + * The hardware reset path bypasses this ID check. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_reset(struct ads131m_priv *priv, struct reset_control *rstc) +{ + if (rstc) + return ads131m_hw_reset(priv, rstc); + + return ads131m_sw_reset(priv); +} + +static int ads131m_power_init(struct ads131m_priv *priv) +{ + static const char * const supply_ids[] = { "avdd", "dvdd" }; + struct device *dev = &priv->spi->dev; + int vref_uV; + int ret; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(supply_ids), supply_ids); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to enable regulators\n"); + + /* Default to Internal 1.2V reference: 1200mV / 2^23 */ + priv->scale_val = ADS131M_VREF_INTERNAL_mV; + priv->scale_val2 = BIT(ADS131M_CODE_BITS); + + if (!priv->config->supports_extref) + return 0; + + ret = devm_regulator_get_enable_read_voltage(dev, "refin"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to get refin supply\n"); + + if (ret == 0) + return dev_err_probe(dev, -EINVAL, "refin supply reports 0V\n"); + + if (ret == -ENODEV) + return 0; + + vref_uV = ret; + + /* + * External reference found: Scale(mV) = (vref_uV * 0.96) / 1000 + * The denominator is 100 * 2^23 because of the 0.96 factor (96/100). + */ + priv->scale_val = div_s64((s64)vref_uV * ADS131M_EXTREF_SCALE_NUM, 1000); + priv->scale_val2 = ADS131M_EXTREF_SCALE_DEN * BIT(ADS131M_CODE_BITS); + priv->use_external_ref = true; + + return 0; +} + +/** + * ads131m_hw_init - Initialize the ADC hardware. + * @priv: Device private data structure. + * @rstc: Optional reset control, or NULL for software reset. + * @is_xtal: True if 'clock-names' is "xtal", false if "clkin". + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_hw_init(struct ads131m_priv *priv, + struct reset_control *rstc, bool is_xtal) +{ + struct device *dev = &priv->spi->dev; + u16 mode_clear, mode_set; + int ret; + + ret = ads131m_reset(priv, rstc); + if (ret < 0) + return ret; + + /* + * Configure CLOCK register (0x03) based on DT properties. + * This register only needs configuration for 32-pin (M06/M08) + * variants, as the configurable bits (XTAL_DIS, EXTREF_EN) + * are reserved on 20-pin (M02/M03/M04) variants. + */ + if (priv->config->supports_xtal || priv->config->supports_extref) { + u16 clk_set = 0; + + if (priv->config->supports_xtal && !is_xtal) + clk_set |= ADS131M_CLOCK_XTAL_DIS; + + if (priv->config->supports_extref && priv->use_external_ref) + clk_set |= ADS131M_CLOCK_EXTREF_EN; + + ret = ads131m_rmw_reg(priv, ADS131M_REG_CLOCK, + ADS131M_CLOCK_EXTREF_EN | ADS131M_CLOCK_XTAL_DIS, + clk_set); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to configure CLOCK register\n"); + } + + /* + * The RESET command sets all registers to default, which means: + * 1. The RESET bit (Bit 10) in MODE is set to '1'. + * 2. The CRC_TYPE bit (Bit 11) in MODE is '0' (CCITT). + * 3. The RX_CRC_EN bit (Bit 12) in MODE is '0' (Disabled). + * + * We must: + * 1. Clear the RESET bit. + * 2. Enable Input CRC (RX_CRC_EN). + * 3. Explicitly clear the ANSI CRC bit (for certainty). + */ + mode_clear = ADS131M_MODE_CRC_TYPE_ANSI | ADS131M_MODE_RESET_FLAG; + mode_set = ADS131M_MODE_RX_CRC_EN; + + ret = ads131m_rmw_reg(priv, ADS131M_REG_MODE, mode_clear, mode_set); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to configure MODE register\n"); + + return 0; +} + +/** + * ads131m_parse_clock - enable clock and detect "xtal" selection + * @priv: Device private data structure. + * @is_xtal: result flag (true if "xtal", false if default "clkin") + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_parse_clock(struct ads131m_priv *priv, bool *is_xtal) +{ + struct device *dev = &priv->spi->dev; + struct clk *clk; + int ret; + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR_OR_NULL(clk)) { + if (IS_ERR(clk)) + ret = PTR_ERR(clk); + else + ret = -ENODEV; + + return dev_err_probe(dev, ret, "clk get enabled failed\n"); + } + + ret = device_property_match_string(dev, "clock-names", "xtal"); + if (ret > 0) + return dev_err_probe(dev, -EINVAL, + "'xtal' must be the only or first clock name"); + + if (ret < 0 && ret != -ENODATA) + return dev_err_probe(dev, ret, + "failed to read 'clock-names' property"); + + if (ret == 0 && !priv->config->supports_xtal) + return dev_err_probe(dev, -EINVAL, + "'xtal' clock not supported on this device"); + + *is_xtal = !ret; + + return 0; +} + +static int ads131m_probe(struct spi_device *spi) +{ + const struct ads131m_configuration *config; + struct device *dev = &spi->dev; + struct reset_control *rstc; + struct iio_dev *indio_dev; + struct ads131m_priv *priv; + bool is_xtal; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + priv = iio_priv(indio_dev); + priv->indio_dev = indio_dev; + priv->spi = spi; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ads131m_info; + + config = spi_get_device_match_data(spi); + + priv->config = config; + indio_dev->name = config->name; + indio_dev->channels = config->channels; + indio_dev->num_channels = config->num_channels; + + rstc = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(rstc)) + return dev_err_probe(dev, PTR_ERR(rstc), + "Failed to get reset controller\n"); + + ret = devm_mutex_init(dev, &priv->lock); + if (ret < 0) + return ret; + + ret = ads131m_prepare_message(priv); + if (ret < 0) + return ret; + + ret = ads131m_power_init(priv); + if (ret < 0) + return ret; + + /* Power must be applied and stable before the clock is enabled. */ + ret = ads131m_parse_clock(priv, &is_xtal); + if (ret < 0) + return ret; + + ret = ads131m_hw_init(priv, rstc, is_xtal); + if (ret < 0) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ads131m_of_match[] = { + { .compatible = "ti,ads131m02", .data = &ads131m02_config }, + { .compatible = "ti,ads131m03", .data = &ads131m03_config }, + { .compatible = "ti,ads131m04", .data = &ads131m04_config }, + { .compatible = "ti,ads131m06", .data = &ads131m06_config }, + { .compatible = "ti,ads131m08", .data = &ads131m08_config }, + { } +}; +MODULE_DEVICE_TABLE(of, ads131m_of_match); + +static const struct spi_device_id ads131m_id[] = { + { "ads131m02", (kernel_ulong_t)&ads131m02_config }, + { "ads131m03", (kernel_ulong_t)&ads131m03_config }, + { "ads131m04", (kernel_ulong_t)&ads131m04_config }, + { "ads131m06", (kernel_ulong_t)&ads131m06_config }, + { "ads131m08", (kernel_ulong_t)&ads131m08_config }, + { } +}; +MODULE_DEVICE_TABLE(spi, ads131m_id); + +static struct spi_driver ads131m_driver = { + .driver = { + .name = "ads131m02", + .of_match_table = ads131m_of_match, + }, + .probe = ads131m_probe, + .id_table = ads131m_id, +}; +module_spi_driver(ads131m_driver); + +MODULE_AUTHOR("David Jander "); +MODULE_DESCRIPTION("Texas Instruments ADS131M02 ADC driver"); +MODULE_LICENSE("GPL"); From 048a15b7211ada26d170b5a61248fb3356739976 Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Wed, 12 Nov 2025 23:06:38 +0100 Subject: [PATCH 021/297] iio: accel: Change adxl345 depend to negate adxl35x Change 'depends on INPUT_ADXL34X=n' to '!(INPUT_ADXL34X)' to allow both drivers to be compiled as modules. The user then can use the blacklist to block loading one. Signed-off-by: Jorge Marques Signed-off-by: Jonathan Cameron --- drivers/iio/accel/Kconfig | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index 76911278fb21..3d3f8d8673dd 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -64,7 +64,7 @@ config ADXL345 config ADXL345_I2C tristate "Analog Devices ADXL345 3-Axis Digital Accelerometer I2C Driver" - depends on INPUT_ADXL34X=n + depends on !INPUT_ADXL34X depends on I2C select ADXL345 select REGMAP_I2C @@ -74,11 +74,12 @@ config ADXL345_I2C To compile this driver as a module, choose M here: the module will be called adxl345_i2c and you will also get adxl345_core - for the core module. + for the core module. INPUT_ADXL34X share compatibles with this + driver, do not add both modules to the kernel. config ADXL345_SPI tristate "Analog Devices ADXL345 3-Axis Digital Accelerometer SPI Driver" - depends on INPUT_ADXL34X=n + depends on !INPUT_ADXL34X depends on SPI select ADXL345 select REGMAP_SPI @@ -88,7 +89,8 @@ config ADXL345_SPI To compile this driver as a module, choose M here: the module will be called adxl345_spi and you will also get adxl345_core - for the core module. + for the core module. INPUT_ADXL34X share compatibles with this + driver, do not add both modules to the kernel. config ADXL355 tristate From a19489ca82bb5cedfe348326905fb66d66ffac65 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Mon, 8 Dec 2025 03:08:18 +0100 Subject: [PATCH 022/297] dt-bindings: iio: adc: Add the NXP SAR ADC for s32g2/3 platforms The s32g2 and s32g3 NXP platforms have two instances of a Successive Approximation Register ADC. It supports the raw, trigger and scan modes which involves the DMA. Add their descriptions. Signed-off-by: Daniel Lezcano Reviewed-by: Rob Herring (Arm) Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/nxp,s32g2-sar-adc.yaml | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/nxp,s32g2-sar-adc.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/nxp,s32g2-sar-adc.yaml b/Documentation/devicetree/bindings/iio/adc/nxp,s32g2-sar-adc.yaml new file mode 100644 index 000000000000..ec258f224df8 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/nxp,s32g2-sar-adc.yaml @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/nxp,s32g2-sar-adc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NXP Successive Approximation ADC + +description: + The NXP SAR ADC provides fast and accurate analog-to-digital + conversion using the Successive Approximation Register (SAR) method. + It has 12-bit resolution with 8 input channels. Conversions can be + launched in software or using hardware triggers. It supports + continuous and one-shot modes with separate registers. + +maintainers: + - Daniel Lezcano + +properties: + compatible: + oneOf: + - const: nxp,s32g2-sar-adc + - items: + - const: nxp,s32g3-sar-adc + - const: nxp,s32g2-sar-adc + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + dmas: + maxItems: 1 + + dma-names: + const: rx + +required: + - compatible + - reg + - interrupts + - clocks + - dmas + - dma-names + +additionalProperties: false + +examples: + - | + #include + + adc@401f8000 { + compatible = "nxp,s32g2-sar-adc"; + reg = <0x401f8000 0x1000>; + interrupts = ; + clocks = <&clks 0x41>; + dmas = <&edma0 0 32>; + dma-names = "rx"; + }; From 4434072a893e4864519c167947083ff3e4cc2d95 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Mon, 8 Dec 2025 03:08:19 +0100 Subject: [PATCH 023/297] iio: adc: Add the NXP SAR ADC support for the s32g2/3 platforms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The NXP S32G2 and S32G3 platforms integrate a successive approximation register (SAR) ADC. Two instances are available, each providing 8 multiplexed input channels with 12-bit resolution. The conversion rate is up to 1 Msps depending on the configuration and sampling window. The SAR ADC supports raw, buffer, and trigger modes. It can operate in both single-shot and continuous conversion modes, with optional hardware triggering through the cross-trigger unit (CTU) or external events. An internal prescaler allows adjusting the sampling clock, while per-channel programmable sampling times provide fine-grained trade-offs between accuracy and latency. Automatic calibration is performed at probe time to minimize offset and gain errors. All modes have been validated on the S32G274-RDB2 platform using an externally generated square wave captured by the ADC. Tests covered buffered streaming via IIO, trigger synchronization, and accuracy verification against a precision laboratory signal source. One potential scenario, not detected during testing, is that in some corner cases the DMA may already have been armed for the next transfer, which can lead dmaengine_tx_status() to return an incorrect residue. The callback_result() operation—intended to supply the residue directly and eliminate the need to call dmaengine_tx_status()—also does not work. Attempting to use dmaengine_pause() and dmaengine_resume() to prevent the residue from being updated does not work either. This potential scenario should apply to any driver using cyclic DMA. However, no current driver actually handles this case, and they all rely on the same acquisition routine (e.g., the STM32 implementation). The NXP SAR acquisition routine has been used in production for several years, which is a good indication of its robustness. As the IIO is implementing the cyclic DMA support API, it is not worth to do more spins to the current routine as it will go away when the new API will be available. The driver is derived from the BSP implementation and has been partly rewritten to comply with upstream requirements. For this reason, all contributors to the original code are listed as co-developers. Originally-by: Stefan-Gabriel Mirea Co-developed-by: Alexandru-Catalin Ionita Signed-off-by: Alexandru-Catalin Ionita Co-developed-by: Ciprian Costea Signed-off-by: Ciprian Costea Co-developed-by: Radu Pirea (NXP OSS) Signed-off-by: Radu Pirea (NXP OSS) Signed-off-by: Daniel Lezcano Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/adc/Kconfig | 12 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/nxp-sar-adc.c | 1016 +++++++++++++++++++++++++++++++++ 3 files changed, 1029 insertions(+) create mode 100644 drivers/iio/adc/nxp-sar-adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 9c4c1e23090a..48e560b7b851 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1222,6 +1222,18 @@ config NPCM_ADC This driver can also be built as a module. If so, the module will be called npcm_adc. +config NXP_SAR_ADC + tristate "NXP S32G SAR-ADC driver" + depends on ARCH_S32 || COMPILE_TEST + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for S32G platforms + analog-to-digital converter. + + This driver can also be built as a module. If so, the module will be + called nxp_sar_adc. + config PAC1921 tristate "Microchip Technology PAC1921 driver" depends on I2C diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index ca3f87510331..05b01c00429f 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -108,6 +108,7 @@ obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o obj-$(CONFIG_NAU7802) += nau7802.o obj-$(CONFIG_NCT7201) += nct7201.o obj-$(CONFIG_NPCM_ADC) += npcm_adc.o +obj-$(CONFIG_NXP_SAR_ADC) += nxp-sar-adc.o obj-$(CONFIG_PAC1921) += pac1921.o obj-$(CONFIG_PAC1934) += pac1934.o obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o diff --git a/drivers/iio/adc/nxp-sar-adc.c b/drivers/iio/adc/nxp-sar-adc.c new file mode 100644 index 000000000000..9efa883c277d --- /dev/null +++ b/drivers/iio/adc/nxp-sar-adc.c @@ -0,0 +1,1016 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP SAR-ADC driver (adapted from Freescale Vybrid vf610 ADC driver + * by Fugang Duan ) + * + * Copyright 2013 Freescale Semiconductor, Inc. + * Copyright 2017, 2020-2025 NXP + * Copyright 2025, Linaro Ltd + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* SAR ADC registers. */ +#define NXP_SAR_ADC_CDR(__base, __channel) (((__base) + 0x100) + ((__channel) * 0x4)) + +#define NXP_SAR_ADC_CDR_CDATA_MASK GENMASK(11, 0) +#define NXP_SAR_ADC_CDR_VALID BIT(19) + +/* Main Configuration Register */ +#define NXP_SAR_ADC_MCR(__base) ((__base) + 0x00) + +#define NXP_SAR_ADC_MCR_PWDN BIT(0) +#define NXP_SAR_ADC_MCR_ACKO BIT(5) +#define NXP_SAR_ADC_MCR_ADCLKSEL BIT(8) +#define NXP_SAR_ADC_MCR_TSAMP_MASK GENMASK(10, 9) +#define NXP_SAR_ADC_MCR_NRSMPL_MASK GENMASK(12, 11) +#define NXP_SAR_ADC_MCR_AVGEN BIT(13) +#define NXP_SAR_ADC_MCR_CALSTART BIT(14) +#define NXP_SAR_ADC_MCR_NSTART BIT(24) +#define NXP_SAR_ADC_MCR_MODE BIT(29) +#define NXP_SAR_ADC_MCR_OWREN BIT(31) + +/* Main Status Register */ +#define NXP_SAR_ADC_MSR(__base) ((__base) + 0x04) + +#define NXP_SAR_ADC_MSR_CALBUSY BIT(29) +#define NXP_SAR_ADC_MSR_CALFAIL BIT(30) + +/* Interrupt Status Register */ +#define NXP_SAR_ADC_ISR(__base) ((__base) + 0x10) + +#define NXP_SAR_ADC_ISR_ECH BIT(0) + +/* Channel Pending Register */ +#define NXP_SAR_ADC_CEOCFR0(__base) ((__base) + 0x14) +#define NXP_SAR_ADC_CEOCFR1(__base) ((__base) + 0x18) + +#define NXP_SAR_ADC_EOC_CH(c) BIT(c) + +/* Interrupt Mask Register */ +#define NXP_SAR_ADC_IMR(__base) ((__base) + 0x20) + +/* Channel Interrupt Mask Register */ +#define NXP_SAR_ADC_CIMR0(__base) ((__base) + 0x24) +#define NXP_SAR_ADC_CIMR1(__base) ((__base) + 0x28) + +/* DMA Setting Register */ +#define NXP_SAR_ADC_DMAE(__base) ((__base) + 0x40) + +#define NXP_SAR_ADC_DMAE_DMAEN BIT(0) +#define NXP_SAR_ADC_DMAE_DCLR BIT(1) + +/* DMA Control register */ +#define NXP_SAR_ADC_DMAR0(__base) ((__base) + 0x44) +#define NXP_SAR_ADC_DMAR1(__base) ((__base) + 0x48) + +/* Conversion Timing Register */ +#define NXP_SAR_ADC_CTR0(__base) ((__base) + 0x94) +#define NXP_SAR_ADC_CTR1(__base) ((__base) + 0x98) + +#define NXP_SAR_ADC_CTR_INPSAMP_MIN 0x08 +#define NXP_SAR_ADC_CTR_INPSAMP_MAX 0xff + +/* Normal Conversion Mask Register */ +#define NXP_SAR_ADC_NCMR0(__base) ((__base) + 0xa4) +#define NXP_SAR_ADC_NCMR1(__base) ((__base) + 0xa8) + +/* Normal Conversion Mask Register field define */ +#define NXP_SAR_ADC_CH_MASK GENMASK(7, 0) + +/* Other field define */ +#define NXP_SAR_ADC_CONV_TIMEOUT (msecs_to_jiffies(100)) +#define NXP_SAR_ADC_CAL_TIMEOUT_US (100 * USEC_PER_MSEC) +#define NXP_SAR_ADC_WAIT_US (2 * USEC_PER_MSEC) +#define NXP_SAR_ADC_RESOLUTION 12 + +/* Duration of conversion phases */ +#define NXP_SAR_ADC_TPT 2 +#define NXP_SAR_ADC_DP 2 +#define NXP_SAR_ADC_CT ((NXP_SAR_ADC_RESOLUTION + 2) * 4) +#define NXP_SAR_ADC_CONV_TIME (NXP_SAR_ADC_TPT + NXP_SAR_ADC_CT + NXP_SAR_ADC_DP) + +#define NXP_SAR_ADC_NR_CHANNELS 8 + +#define NXP_PAGE_SIZE SZ_4K +#define NXP_SAR_ADC_DMA_SAMPLE_SZ DMA_SLAVE_BUSWIDTH_4_BYTES +#define NXP_SAR_ADC_DMA_BUFF_SZ (NXP_PAGE_SIZE * NXP_SAR_ADC_DMA_SAMPLE_SZ) +#define NXP_SAR_ADC_DMA_SAMPLE_CNT (NXP_SAR_ADC_DMA_BUFF_SZ / NXP_SAR_ADC_DMA_SAMPLE_SZ) + +struct nxp_sar_adc { + void __iomem *regs; + phys_addr_t regs_phys; + u8 current_channel; + u8 channels_used; + u16 value; + u32 vref_mV; + + /* Save and restore context. */ + u32 inpsamp; + u32 pwdn; + + struct clk *clk; + struct dma_chan *dma_chan; + struct completion completion; + struct circ_buf dma_buf; + + dma_addr_t rx_dma_buf; + dma_cookie_t cookie; + + /* Protect circular buffers access. */ + spinlock_t lock; + + /* Array of enabled channels. */ + u16 buffered_chan[NXP_SAR_ADC_NR_CHANNELS]; + + /* Buffer to be filled by the DMA. */ + IIO_DECLARE_BUFFER_WITH_TS(u16, buffer, NXP_SAR_ADC_NR_CHANNELS); +}; + +struct nxp_sar_adc_data { + u32 vref_mV; + const char *model; +}; + +#define ADC_CHAN(_idx, _chan_type) { \ + .type = (_chan_type), \ + .indexed = 1, \ + .channel = (_idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = (_idx), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + }, \ +} + +static const struct iio_chan_spec nxp_sar_adc_iio_channels[] = { + ADC_CHAN(0, IIO_VOLTAGE), + ADC_CHAN(1, IIO_VOLTAGE), + ADC_CHAN(2, IIO_VOLTAGE), + ADC_CHAN(3, IIO_VOLTAGE), + ADC_CHAN(4, IIO_VOLTAGE), + ADC_CHAN(5, IIO_VOLTAGE), + ADC_CHAN(6, IIO_VOLTAGE), + ADC_CHAN(7, IIO_VOLTAGE), + /* + * The NXP SAR ADC documentation marks the channels 8 to 31 as + * "Reserved". Reflect the same in the driver in case new ADC + * variants comes with more channels. + */ + IIO_CHAN_SOFT_TIMESTAMP(32), +}; + +static void nxp_sar_adc_irq_cfg(struct nxp_sar_adc *info, bool enable) +{ + if (enable) + writel(NXP_SAR_ADC_ISR_ECH, NXP_SAR_ADC_IMR(info->regs)); + else + writel(0, NXP_SAR_ADC_IMR(info->regs)); +} + +static bool nxp_sar_adc_set_enabled(struct nxp_sar_adc *info, bool enable) +{ + u32 mcr; + bool pwdn; + + mcr = readl(NXP_SAR_ADC_MCR(info->regs)); + + /* + * Get the current state and return it later. This is used for + * suspend/resume to get the power state + */ + pwdn = FIELD_GET(NXP_SAR_ADC_MCR_PWDN, mcr); + + /* When the enabled flag is not set, we set the power down bit */ + FIELD_MODIFY(NXP_SAR_ADC_MCR_PWDN, &mcr, !enable); + + writel(mcr, NXP_SAR_ADC_MCR(info->regs)); + + /* + * Ensure there are at least three cycles between the + * configuration of NCMR and the setting of NSTART. + */ + if (enable) + ndelay(div64_u64(NSEC_PER_SEC, clk_get_rate(info->clk) * 3)); + + return pwdn; +} + +static inline bool nxp_sar_adc_enable(struct nxp_sar_adc *info) +{ + return nxp_sar_adc_set_enabled(info, true); +} + +static inline bool nxp_sar_adc_disable(struct nxp_sar_adc *info) +{ + return nxp_sar_adc_set_enabled(info, false); +} + +static inline void nxp_sar_adc_calibration_start(void __iomem *base) +{ + u32 mcr = readl(NXP_SAR_ADC_MCR(base)); + + FIELD_MODIFY(NXP_SAR_ADC_MCR_CALSTART, &mcr, 0x1); + + writel(mcr, NXP_SAR_ADC_MCR(base)); +} + +static inline int nxp_sar_adc_calibration_wait(void __iomem *base) +{ + u32 msr, ret; + + ret = readl_poll_timeout(NXP_SAR_ADC_MSR(base), msr, + !FIELD_GET(NXP_SAR_ADC_MSR_CALBUSY, msr), + NXP_SAR_ADC_WAIT_US, + NXP_SAR_ADC_CAL_TIMEOUT_US); + if (ret) + return ret; + + if (FIELD_GET(NXP_SAR_ADC_MSR_CALFAIL, msr)) { + /* + * If the calibration fails, the status register bit must be + * cleared. + */ + FIELD_MODIFY(NXP_SAR_ADC_MSR_CALFAIL, &msr, 0x0); + writel(msr, NXP_SAR_ADC_MSR(base)); + + return -EAGAIN; + } + + return 0; +} + +static int nxp_sar_adc_calibration(struct nxp_sar_adc *info) +{ + int ret; + + /* Calibration works only if the ADC is powered up. */ + nxp_sar_adc_enable(info); + + /* The calibration operation starts. */ + nxp_sar_adc_calibration_start(info->regs); + + ret = nxp_sar_adc_calibration_wait(info->regs); + + /* + * Calibration works only if the ADC is powered up. However + * the calibration is called from the probe function where the + * iio is not enabled, so we disable after the calibration. + */ + nxp_sar_adc_disable(info); + + return ret; +} + +static void nxp_sar_adc_conversion_timing_set(struct nxp_sar_adc *info, u32 inpsamp) +{ + inpsamp = clamp(inpsamp, NXP_SAR_ADC_CTR_INPSAMP_MIN, NXP_SAR_ADC_CTR_INPSAMP_MAX); + + writel(inpsamp, NXP_SAR_ADC_CTR0(info->regs)); +} + +static u32 nxp_sar_adc_conversion_timing_get(struct nxp_sar_adc *info) +{ + return readl(NXP_SAR_ADC_CTR0(info->regs)); +} + +static void nxp_sar_adc_read_notify(struct nxp_sar_adc *info) +{ + writel(NXP_SAR_ADC_CH_MASK, NXP_SAR_ADC_CEOCFR0(info->regs)); + writel(NXP_SAR_ADC_CH_MASK, NXP_SAR_ADC_CEOCFR1(info->regs)); +} + +static int nxp_sar_adc_read_data(struct nxp_sar_adc *info, unsigned int chan) +{ + u32 ceocfr, cdr; + + ceocfr = readl(NXP_SAR_ADC_CEOCFR0(info->regs)); + + /* + * FIELD_GET() can not be used here because EOC_CH is not constant. + * TODO: Switch to field_get() when it will be available. + */ + if (!(NXP_SAR_ADC_EOC_CH(chan) & ceocfr)) + return -EIO; + + cdr = readl(NXP_SAR_ADC_CDR(info->regs, chan)); + if (!(FIELD_GET(NXP_SAR_ADC_CDR_VALID, cdr))) + return -EIO; + + return FIELD_GET(NXP_SAR_ADC_CDR_CDATA_MASK, cdr); +} + +static void nxp_sar_adc_isr_buffer(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + unsigned int i; + int ret; + + for (i = 0; i < info->channels_used; i++) { + ret = nxp_sar_adc_read_data(info, info->buffered_chan[i]); + if (ret < 0) { + nxp_sar_adc_read_notify(info); + return; + } + + info->buffer[i] = ret; + } + + nxp_sar_adc_read_notify(info); + + iio_push_to_buffers_with_ts(indio_dev, info->buffer, sizeof(info->buffer), + iio_get_time_ns(indio_dev)); + + iio_trigger_notify_done(indio_dev->trig); +} + +static void nxp_sar_adc_isr_read_raw(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + int ret; + + ret = nxp_sar_adc_read_data(info, info->current_channel); + nxp_sar_adc_read_notify(info); + if (ret < 0) + return; + + info->value = ret; + complete(&info->completion); +} + +static irqreturn_t nxp_sar_adc_isr(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct nxp_sar_adc *info = iio_priv(indio_dev); + int isr; + + isr = readl(NXP_SAR_ADC_ISR(info->regs)); + if (!(FIELD_GET(NXP_SAR_ADC_ISR_ECH, isr))) + return IRQ_NONE; + + if (iio_buffer_enabled(indio_dev)) + nxp_sar_adc_isr_buffer(indio_dev); + else + nxp_sar_adc_isr_read_raw(indio_dev); + + writel(NXP_SAR_ADC_ISR_ECH, NXP_SAR_ADC_ISR(info->regs)); + + return IRQ_HANDLED; +} + +static void nxp_sar_adc_channels_disable(struct nxp_sar_adc *info, u32 mask) +{ + u32 ncmr, cimr; + + ncmr = readl(NXP_SAR_ADC_NCMR0(info->regs)); + cimr = readl(NXP_SAR_ADC_CIMR0(info->regs)); + + /* FIELD_MODIFY() can not be used because the mask is not constant */ + ncmr &= ~mask; + cimr &= ~mask; + + writel(ncmr, NXP_SAR_ADC_NCMR0(info->regs)); + writel(cimr, NXP_SAR_ADC_CIMR0(info->regs)); +} + +static void nxp_sar_adc_channels_enable(struct nxp_sar_adc *info, u32 mask) +{ + u32 ncmr, cimr; + + ncmr = readl(NXP_SAR_ADC_NCMR0(info->regs)); + cimr = readl(NXP_SAR_ADC_CIMR0(info->regs)); + + ncmr |= mask; + cimr |= mask; + + writel(ncmr, NXP_SAR_ADC_NCMR0(info->regs)); + writel(cimr, NXP_SAR_ADC_CIMR0(info->regs)); +} + +static void nxp_sar_adc_dma_channels_enable(struct nxp_sar_adc *info, u32 mask) +{ + u32 dmar; + + dmar = readl(NXP_SAR_ADC_DMAR0(info->regs)); + + dmar |= mask; + + writel(dmar, NXP_SAR_ADC_DMAR0(info->regs)); +} + +static void nxp_sar_adc_dma_channels_disable(struct nxp_sar_adc *info, u32 mask) +{ + u32 dmar; + + dmar = readl(NXP_SAR_ADC_DMAR0(info->regs)); + + dmar &= ~mask; + + writel(dmar, NXP_SAR_ADC_DMAR0(info->regs)); +} + +static void nxp_sar_adc_dma_cfg(struct nxp_sar_adc *info, bool enable) +{ + u32 dmae; + + dmae = readl(NXP_SAR_ADC_DMAE(info->regs)); + + FIELD_MODIFY(NXP_SAR_ADC_DMAE_DMAEN, &dmae, enable); + + writel(dmae, NXP_SAR_ADC_DMAE(info->regs)); +} + +static void nxp_sar_adc_stop_conversion(struct nxp_sar_adc *info) +{ + u32 mcr; + + mcr = readl(NXP_SAR_ADC_MCR(info->regs)); + + FIELD_MODIFY(NXP_SAR_ADC_MCR_NSTART, &mcr, 0x0); + + writel(mcr, NXP_SAR_ADC_MCR(info->regs)); + + /* + * On disable, we have to wait for the transaction to finish. + * ADC does not abort the transaction if a chain conversion is + * in progress. Wait for the worst case scenario - 80 ADC clk + * cycles. The clock rate is 80MHz, this routine is called + * only when the capture finishes. The delay will be very + * short, usec-ish, which is acceptable in the atomic context. + */ + ndelay(div64_u64(NSEC_PER_SEC, clk_get_rate(info->clk)) * 80); +} + +static int nxp_sar_adc_start_conversion(struct nxp_sar_adc *info, bool raw) +{ + u32 mcr; + + mcr = readl(NXP_SAR_ADC_MCR(info->regs)); + + FIELD_MODIFY(NXP_SAR_ADC_MCR_NSTART, &mcr, 0x1); + FIELD_MODIFY(NXP_SAR_ADC_MCR_MODE, &mcr, raw ? 0 : 1); + + writel(mcr, NXP_SAR_ADC_MCR(info->regs)); + + return 0; +} + +static int nxp_sar_adc_read_channel(struct nxp_sar_adc *info, int channel) +{ + int ret; + + info->current_channel = channel; + nxp_sar_adc_channels_enable(info, BIT(channel)); + nxp_sar_adc_irq_cfg(info, true); + nxp_sar_adc_enable(info); + + reinit_completion(&info->completion); + ret = nxp_sar_adc_start_conversion(info, true); + if (ret < 0) + goto out_disable; + + if (!wait_for_completion_interruptible_timeout(&info->completion, + NXP_SAR_ADC_CONV_TIMEOUT)) + ret = -ETIMEDOUT; + + nxp_sar_adc_stop_conversion(info); + +out_disable: + nxp_sar_adc_channels_disable(info, BIT(channel)); + nxp_sar_adc_irq_cfg(info, false); + nxp_sar_adc_disable(info); + + return ret; +} + +static int nxp_sar_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + u32 inpsamp; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = nxp_sar_adc_read_channel(info, chan->channel); + + iio_device_release_direct(indio_dev); + + if (ret) + return ret; + + *val = info->value; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = info->vref_mV; + *val2 = NXP_SAR_ADC_RESOLUTION; + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_SAMP_FREQ: + inpsamp = nxp_sar_adc_conversion_timing_get(info); + *val = clk_get_rate(info->clk) / (inpsamp + NXP_SAR_ADC_CONV_TIME); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int nxp_sar_adc_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + u32 inpsamp; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + /* + * Configures the sample period duration in terms of the SAR + * controller clock. The minimum acceptable value is 8. + * Configuring it to a value lower than 8 sets the sample period + * to 8 cycles. We read the clock value and divide by the + * sampling timing which gives us the number of cycles expected. + * The value is 8-bit wide, consequently the max value is 0xFF. + */ + inpsamp = clk_get_rate(info->clk) / val - NXP_SAR_ADC_CONV_TIME; + nxp_sar_adc_conversion_timing_set(info, inpsamp); + return 0; + + default: + return -EINVAL; + } +} + +static void nxp_sar_adc_dma_cb(void *data) +{ + struct iio_dev *indio_dev = data; + struct nxp_sar_adc *info = iio_priv(indio_dev); + struct dma_tx_state state; + struct circ_buf *dma_buf; + struct device *dev_dma; + u32 *dma_samples; + s64 timestamp; + int idx, ret; + + guard(spinlock_irqsave)(&info->lock); + + dma_buf = &info->dma_buf; + dma_samples = (u32 *)dma_buf->buf; + dev_dma = info->dma_chan->device->dev; + + /* + * DMA in some corner cases might have already be charged for + * the next transfer. Potentially there can be a race where + * the residue changes while the dma engine updates the + * buffer. That could be handled by using the + * callback_result() instead of callback() because the residue + * will be passed as a parameter to the function. However this + * new callback is pretty new and the backend does not update + * the residue. So let's stick to the version other drivers do + * which has proven running well in production since several + * years. + */ + dmaengine_tx_status(info->dma_chan, info->cookie, &state); + + dma_sync_single_for_cpu(dev_dma, info->rx_dma_buf, + NXP_SAR_ADC_DMA_BUFF_SZ, DMA_FROM_DEVICE); + + /* Current head position. */ + dma_buf->head = (NXP_SAR_ADC_DMA_BUFF_SZ - state.residue) / + NXP_SAR_ADC_DMA_SAMPLE_SZ; + + /* If everything was transferred, avoid an off by one error. */ + if (!state.residue) + dma_buf->head--; + + /* Something went wrong and nothing transferred. */ + if (state.residue != NXP_SAR_ADC_DMA_BUFF_SZ) { + /* Make sure that head is multiple of info->channels_used. */ + dma_buf->head -= dma_buf->head % info->channels_used; + + /* + * dma_buf->tail != dma_buf->head condition will become false + * because dma_buf->tail will be incremented with 1. + */ + while (dma_buf->tail != dma_buf->head) { + idx = dma_buf->tail % info->channels_used; + info->buffer[idx] = dma_samples[dma_buf->tail]; + dma_buf->tail = (dma_buf->tail + 1) % NXP_SAR_ADC_DMA_SAMPLE_CNT; + if (idx != info->channels_used - 1) + continue; + + /* + * iio_push_to_buffers_with_ts() should not be + * called with dma_samples as parameter. The samples + * will be smashed if timestamp is enabled. + */ + timestamp = iio_get_time_ns(indio_dev); + ret = iio_push_to_buffers_with_ts(indio_dev, info->buffer, + sizeof(info->buffer), + timestamp); + if (ret < 0 && ret != -EBUSY) + dev_err_ratelimited(&indio_dev->dev, + "failed to push iio buffer: %d", + ret); + } + + dma_buf->tail = dma_buf->head; + } + + dma_sync_single_for_device(dev_dma, info->rx_dma_buf, + NXP_SAR_ADC_DMA_BUFF_SZ, DMA_FROM_DEVICE); +} + +static int nxp_sar_adc_start_cyclic_dma(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + struct dma_slave_config config; + struct dma_async_tx_descriptor *desc; + int ret; + + info->dma_buf.head = 0; + info->dma_buf.tail = 0; + + config.direction = DMA_DEV_TO_MEM; + config.src_addr_width = NXP_SAR_ADC_DMA_SAMPLE_SZ; + config.src_addr = NXP_SAR_ADC_CDR(info->regs_phys, info->buffered_chan[0]); + config.src_port_window_size = info->channels_used; + config.src_maxburst = info->channels_used; + ret = dmaengine_slave_config(info->dma_chan, &config); + if (ret < 0) + return ret; + + desc = dmaengine_prep_dma_cyclic(info->dma_chan, + info->rx_dma_buf, + NXP_SAR_ADC_DMA_BUFF_SZ, + NXP_SAR_ADC_DMA_BUFF_SZ / 2, + DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); + if (!desc) + return -EINVAL; + + desc->callback = nxp_sar_adc_dma_cb; + desc->callback_param = indio_dev; + info->cookie = dmaengine_submit(desc); + ret = dma_submit_error(info->cookie); + if (ret) { + dmaengine_terminate_async(info->dma_chan); + return ret; + } + + dma_async_issue_pending(info->dma_chan); + + return 0; +} + +static void nxp_sar_adc_buffer_software_do_predisable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + + /* + * The ADC DMAEN bit should be cleared before DMA transaction + * is canceled. + */ + nxp_sar_adc_stop_conversion(info); + dmaengine_terminate_sync(info->dma_chan); + nxp_sar_adc_dma_cfg(info, false); + nxp_sar_adc_dma_channels_disable(info, *indio_dev->active_scan_mask); + + dma_release_channel(info->dma_chan); +} + +static int nxp_sar_adc_buffer_software_do_postenable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + int ret; + + nxp_sar_adc_dma_channels_enable(info, *indio_dev->active_scan_mask); + + nxp_sar_adc_dma_cfg(info, true); + + ret = nxp_sar_adc_start_cyclic_dma(indio_dev); + if (ret) + goto out_dma_channels_disable; + + ret = nxp_sar_adc_start_conversion(info, false); + if (ret) + goto out_stop_cyclic_dma; + + return 0; + +out_stop_cyclic_dma: + dmaengine_terminate_sync(info->dma_chan); + +out_dma_channels_disable: + nxp_sar_adc_dma_cfg(info, false); + nxp_sar_adc_dma_channels_disable(info, *indio_dev->active_scan_mask); + + return ret; +} + +static void nxp_sar_adc_buffer_trigger_do_predisable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + + nxp_sar_adc_irq_cfg(info, false); +} + +static int nxp_sar_adc_buffer_trigger_do_postenable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + + nxp_sar_adc_irq_cfg(info, true); + + return 0; +} + +static int nxp_sar_adc_buffer_postenable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + int current_mode = iio_device_get_current_mode(indio_dev); + unsigned long channel; + int ret; + + info->dma_chan = dma_request_chan(indio_dev->dev.parent, "rx"); + if (IS_ERR(info->dma_chan)) + return PTR_ERR(info->dma_chan); + + info->channels_used = 0; + + /* + * The SAR-ADC has two groups of channels. + * + * - Group #0: + * * bit 0-7 : channel 0 -> channel 7 + * * bit 8-31 : reserved + * + * - Group #32: + * * bit 0-7 : Internal + * * bit 8-31 : reserved + * + * The 8 channels from group #0 are used in this driver for + * ADC as described when declaring the IIO device and the + * mapping is the same. That means the active_scan_mask can be + * used directly to write the channel interrupt mask. + */ + nxp_sar_adc_channels_enable(info, *indio_dev->active_scan_mask); + + for_each_set_bit(channel, indio_dev->active_scan_mask, NXP_SAR_ADC_NR_CHANNELS) + info->buffered_chan[info->channels_used++] = channel; + + nxp_sar_adc_enable(info); + + if (current_mode == INDIO_BUFFER_SOFTWARE) + ret = nxp_sar_adc_buffer_software_do_postenable(indio_dev); + else + ret = nxp_sar_adc_buffer_trigger_do_postenable(indio_dev); + if (ret) + goto out_postenable; + + return 0; + +out_postenable: + nxp_sar_adc_disable(info); + nxp_sar_adc_channels_disable(info, *indio_dev->active_scan_mask); + + return ret; +} + +static int nxp_sar_adc_buffer_predisable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + int currentmode = iio_device_get_current_mode(indio_dev); + + if (currentmode == INDIO_BUFFER_SOFTWARE) + nxp_sar_adc_buffer_software_do_predisable(indio_dev); + else + nxp_sar_adc_buffer_trigger_do_predisable(indio_dev); + + nxp_sar_adc_disable(info); + + nxp_sar_adc_channels_disable(info, *indio_dev->active_scan_mask); + + return 0; +} + +static irqreturn_t nxp_sar_adc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct nxp_sar_adc *info = iio_priv(indio_dev); + int ret; + + ret = nxp_sar_adc_start_conversion(info, true); + if (ret < 0) + dev_dbg(&indio_dev->dev, "Failed to start conversion\n"); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = { + .postenable = nxp_sar_adc_buffer_postenable, + .predisable = nxp_sar_adc_buffer_predisable, +}; + +static const struct iio_info nxp_sar_adc_iio_info = { + .read_raw = nxp_sar_adc_read_raw, + .write_raw = nxp_sar_adc_write_raw, +}; + +static int nxp_sar_adc_dma_probe(struct device *dev, struct nxp_sar_adc *info) +{ + u8 *rx_buf; + + rx_buf = dmam_alloc_coherent(dev, NXP_SAR_ADC_DMA_BUFF_SZ, + &info->rx_dma_buf, GFP_KERNEL); + if (!rx_buf) + return -ENOMEM; + + info->dma_buf.buf = rx_buf; + + return 0; +} + +/* + * The documentation describes the reset values for the registers. + * However some registers do not have these values after a reset. It + * is not a desirable situation. In some other SoC family + * documentation NXP recommends not assuming the default values are + * set and to initialize the registers conforming to the documentation + * reset information to prevent this situation. Assume the same rule + * applies here as there is a discrepancy between what is read from + * the registers at reset time and the documentation. + */ +static void nxp_sar_adc_set_default_values(struct nxp_sar_adc *info) +{ + writel(0x00003901, NXP_SAR_ADC_MCR(info->regs)); + writel(0x00000001, NXP_SAR_ADC_MSR(info->regs)); + writel(0x00000014, NXP_SAR_ADC_CTR0(info->regs)); + writel(0x00000014, NXP_SAR_ADC_CTR1(info->regs)); + writel(0x00000000, NXP_SAR_ADC_CIMR0(info->regs)); + writel(0x00000000, NXP_SAR_ADC_CIMR1(info->regs)); + writel(0x00000000, NXP_SAR_ADC_NCMR0(info->regs)); + writel(0x00000000, NXP_SAR_ADC_NCMR1(info->regs)); +} + +static int nxp_sar_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct nxp_sar_adc_data *data = device_get_match_data(dev); + struct nxp_sar_adc *info; + struct iio_dev *indio_dev; + struct resource *mem; + int irq, ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + info->vref_mV = data->vref_mV; + spin_lock_init(&info->lock); + info->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem); + if (IS_ERR(info->regs)) + return dev_err_probe(dev, PTR_ERR(info->regs), + "Failed to get and remap resource"); + + info->regs_phys = mem->start; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, nxp_sar_adc_isr, 0, dev_name(dev), + indio_dev); + if (ret < 0) + return ret; + + info->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(info->clk)) + return dev_err_probe(dev, PTR_ERR(info->clk), + "Failed to get the clock\n"); + + platform_set_drvdata(pdev, indio_dev); + + init_completion(&info->completion); + + indio_dev->name = data->model; + indio_dev->info = &nxp_sar_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + indio_dev->channels = nxp_sar_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(nxp_sar_adc_iio_channels); + + nxp_sar_adc_set_default_values(info); + + ret = nxp_sar_adc_calibration(info); + if (ret) + dev_err_probe(dev, ret, "Calibration failed\n"); + + ret = nxp_sar_adc_dma_probe(dev, info); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialize the DMA\n"); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + &iio_pollfunc_store_time, + &nxp_sar_adc_trigger_handler, + &iio_triggered_buffer_setup_ops); + if (ret < 0) + return dev_err_probe(dev, ret, "Couldn't initialise the buffer\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Couldn't register the device\n"); + + return 0; +} + +static int nxp_sar_adc_suspend(struct device *dev) +{ + struct nxp_sar_adc *info = iio_priv(dev_get_drvdata(dev)); + + info->pwdn = nxp_sar_adc_disable(info); + info->inpsamp = nxp_sar_adc_conversion_timing_get(info); + + clk_disable_unprepare(info->clk); + + return 0; +} + +static int nxp_sar_adc_resume(struct device *dev) +{ + struct nxp_sar_adc *info = iio_priv(dev_get_drvdata(dev)); + int ret; + + ret = clk_prepare_enable(info->clk); + if (ret) + return ret; + + nxp_sar_adc_conversion_timing_set(info, info->inpsamp); + + if (!info->pwdn) + nxp_sar_adc_enable(info); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(nxp_sar_adc_pm_ops, nxp_sar_adc_suspend, + nxp_sar_adc_resume); + +static const struct nxp_sar_adc_data s32g2_sar_adc_data = { + .vref_mV = 1800, + .model = "s32g2-sar-adc", +}; + +static const struct of_device_id nxp_sar_adc_match[] = { + { .compatible = "nxp,s32g2-sar-adc", .data = &s32g2_sar_adc_data }, + { } +}; +MODULE_DEVICE_TABLE(of, nxp_sar_adc_match); + +static struct platform_driver nxp_sar_adc_driver = { + .probe = nxp_sar_adc_probe, + .driver = { + .name = "nxp-sar-adc", + .of_match_table = nxp_sar_adc_match, + .pm = pm_sleep_ptr(&nxp_sar_adc_pm_ops), + }, +}; +module_platform_driver(nxp_sar_adc_driver); + +MODULE_AUTHOR("NXP"); +MODULE_DESCRIPTION("NXP SAR-ADC driver"); +MODULE_LICENSE("GPL"); From d4f13bc9aacd1f0effd55ef95316c557f8138bba Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Fri, 12 Dec 2025 16:47:31 +0200 Subject: [PATCH 024/297] dt-bindings: iio: frequency: adf4377: add clk provider Add support for clock provider. Acked-by: Conor Dooley Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/frequency/adi,adf4377.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/frequency/adi,adf4377.yaml b/Documentation/devicetree/bindings/iio/frequency/adi,adf4377.yaml index 5f950ee9aec7..be69b9c68e74 100644 --- a/Documentation/devicetree/bindings/iio/frequency/adi,adf4377.yaml +++ b/Documentation/devicetree/bindings/iio/frequency/adi,adf4377.yaml @@ -40,6 +40,12 @@ properties: items: - const: ref_in + '#clock-cells': + const: 0 + + clock-output-names: + maxItems: 1 + chip-enable-gpios: description: GPIO that controls the Chip Enable Pin. @@ -97,6 +103,8 @@ examples: spi-max-frequency = <10000000>; clocks = <&adf4377_ref_in>; clock-names = "ref_in"; + #clock-cells = <0>; + clock-output-names = "adf4377"; }; }; ... From 60e5448ddbec2dc206027a4850604d8fdf9973b7 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Fri, 12 Dec 2025 16:47:32 +0200 Subject: [PATCH 025/297] iio: frequency: adf4377: add clk provider support Add clk provider feature for the adf4377. Even though the driver was sent as an IIO driver in most cases the device is actually seen as a clock provider. This patch aims to cover actual usecases requested by users in order to completely control the output frequencies from userspace. Reviewed-by: Andy Shevchenko Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- drivers/iio/frequency/adf4377.c | 122 +++++++++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 2 deletions(-) diff --git a/drivers/iio/frequency/adf4377.c b/drivers/iio/frequency/adf4377.c index 08833b7035e4..fa686f785fa4 100644 --- a/drivers/iio/frequency/adf4377.c +++ b/drivers/iio/frequency/adf4377.c @@ -8,7 +8,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -435,9 +437,14 @@ struct adf4377_state { struct gpio_desc *gpio_ce; struct gpio_desc *gpio_enclk1; struct gpio_desc *gpio_enclk2; + struct clk *clk; + struct clk *clkout; + struct clk_hw hw; u8 buf[2] __aligned(IIO_DMA_MINALIGN); }; +#define to_adf4377_state(h) container_of(h, struct adf4377_state, hw) + static const char * const adf4377_muxout_modes[] = { [ADF4377_MUXOUT_HIGH_Z] = "high_z", [ADF4377_MUXOUT_LKDET] = "lock_detect", @@ -929,6 +936,110 @@ static int adf4377_freq_change(struct notifier_block *nb, unsigned long action, return NOTIFY_OK; } +static unsigned long adf4377_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct adf4377_state *st = to_adf4377_state(hw); + u64 freq; + int ret; + + ret = adf4377_get_freq(st, &freq); + if (ret) + return 0; + + return freq; +} + +static int adf4377_clk_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct adf4377_state *st = to_adf4377_state(hw); + + return adf4377_set_freq(st, rate); +} + +static int adf4377_clk_prepare(struct clk_hw *hw) +{ + struct adf4377_state *st = to_adf4377_state(hw); + + return regmap_update_bits(st->regmap, 0x1a, ADF4377_001A_PD_CLKOUT1_MSK | + ADF4377_001A_PD_CLKOUT2_MSK, + FIELD_PREP(ADF4377_001A_PD_CLKOUT1_MSK, 0) | + FIELD_PREP(ADF4377_001A_PD_CLKOUT2_MSK, 0)); +} + +static void adf4377_clk_unprepare(struct clk_hw *hw) +{ + struct adf4377_state *st = to_adf4377_state(hw); + + regmap_update_bits(st->regmap, 0x1a, ADF4377_001A_PD_CLKOUT1_MSK | + ADF4377_001A_PD_CLKOUT2_MSK, + FIELD_PREP(ADF4377_001A_PD_CLKOUT1_MSK, 1) | + FIELD_PREP(ADF4377_001A_PD_CLKOUT2_MSK, 1)); +} + +static int adf4377_clk_is_prepared(struct clk_hw *hw) +{ + struct adf4377_state *st = to_adf4377_state(hw); + unsigned int readval; + int ret; + + ret = regmap_read(st->regmap, 0x1a, &readval); + if (ret) + return ret; + + return !(readval & (ADF4377_001A_PD_CLKOUT1_MSK | ADF4377_001A_PD_CLKOUT2_MSK)); +} + +static const struct clk_ops adf4377_clk_ops = { + .recalc_rate = adf4377_clk_recalc_rate, + .set_rate = adf4377_clk_set_rate, + .prepare = adf4377_clk_prepare, + .unprepare = adf4377_clk_unprepare, + .is_prepared = adf4377_clk_is_prepared, +}; + +static int adf4377_clk_register(struct adf4377_state *st) +{ + struct spi_device *spi = st->spi; + struct device *dev = &spi->dev; + struct clk_init_data init; + struct clk_parent_data parent_data; + int ret; + + if (!device_property_present(dev, "#clock-cells")) + return 0; + + ret = device_property_read_string(dev, "clock-output-names", &init.name); + if (ret) { + init.name = devm_kasprintf(dev, GFP_KERNEL, "%pfw-clk", + dev_fwnode(dev)); + if (!init.name) + return -ENOMEM; + } + + parent_data.fw_name = "ref_in"; + + init.ops = &adf4377_clk_ops; + init.parent_data = &parent_data; + init.num_parents = 1; + init.flags = CLK_SET_RATE_PARENT; + + st->hw.init = &init; + ret = devm_clk_hw_register(dev, &st->hw); + if (ret) + return ret; + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &st->hw); + if (ret) + return ret; + + st->clkout = st->hw.clk; + + return 0; +} + static const struct adf4377_chip_info adf4377_chip_info = { .name = "adf4377", .has_gpio_enclk2 = true, @@ -958,8 +1069,6 @@ static int adf4377_probe(struct spi_device *spi) indio_dev->info = &adf4377_info; indio_dev->name = "adf4377"; - indio_dev->channels = adf4377_channels; - indio_dev->num_channels = ARRAY_SIZE(adf4377_channels); st->regmap = regmap; st->spi = spi; @@ -979,6 +1088,15 @@ static int adf4377_probe(struct spi_device *spi) if (ret) return ret; + ret = adf4377_clk_register(st); + if (ret) + return ret; + + if (!st->clkout) { + indio_dev->channels = adf4377_channels; + indio_dev->num_channels = ARRAY_SIZE(adf4377_channels); + } + return devm_iio_device_register(&spi->dev, indio_dev); } From 09140a720e00e7498435796b4ed648ca3a71cf59 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Fri, 12 Dec 2025 17:38:25 +0200 Subject: [PATCH 026/297] dt-bindings: iio: amplifiers: add adl8113 Add devicetree bindings for the ADL8113 Low Noise Amplifier. The bindings include support for specifying gain values of external amplifiers connected to the two external bypass paths (A and B). These optional properties allow the gain values to be selectable via the hardwaregain attribute, enabling complete devicetree description of the signal chain including external components. Reviewed-by: Rob Herring (Arm) Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- .../bindings/iio/amplifiers/adi,adl8113.yaml | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/amplifiers/adi,adl8113.yaml diff --git a/Documentation/devicetree/bindings/iio/amplifiers/adi,adl8113.yaml b/Documentation/devicetree/bindings/iio/amplifiers/adi,adl8113.yaml new file mode 100644 index 000000000000..6b8491d18139 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/amplifiers/adi,adl8113.yaml @@ -0,0 +1,87 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/amplifiers/adi,adl8113.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices ADL8113 Low Noise Amplifier with integrated bypass switches + +maintainers: + - Antoniu Miclaus + +description: | + The ADL8113 is a 10MHz to 12GHz Low Noise Amplifier with integrated bypass + switches controlled by two GPIO pins (VA and VB). The device supports four + operation modes: + - Internal Amplifier: VA=0, VB=0 - Signal passes through the internal LNA + - Internal Bypass: VA=1, VB=1 - Signal bypasses through internal path + - External Bypass A: VA=0, VB=1 - Signal routes from RFIN to OUT_A and from IN_A to RFOUT + - External Bypass B: VA=1, VB=0 - Signal routes from RFIN to OUT_B and from IN_B to RFOUT + + https://www.analog.com/en/products/adl8113.html + +properties: + compatible: + const: adi,adl8113 + + vdd1-supply: true + + vdd2-supply: true + + vss2-supply: true + + ctrl-gpios: + items: + - description: VA control pin + - description: VB control pin + + adi,external-bypass-a-gain-db: + description: + Gain in dB of external amplifier connected to bypass path A (OUT_A/IN_A). + When specified, this gain value becomes selectable via the hardwaregain + attribute and automatically routes through the external A path. + + adi,external-bypass-b-gain-db: + description: + Gain in dB of external amplifier connected to bypass path B (OUT_B/IN_B). + When specified, this gain value becomes selectable via the hardwaregain + attribute and automatically routes through the external B path. + +required: + - compatible + - ctrl-gpios + - vdd1-supply + - vdd2-supply + - vss2-supply + +additionalProperties: false + +examples: + - | + #include + + /* Basic configuration with only internal paths */ + amplifier { + compatible = "adi,adl8113"; + ctrl-gpios = <&gpio 22 GPIO_ACTIVE_HIGH>, + <&gpio 23 GPIO_ACTIVE_HIGH>; + vdd1-supply = <&vdd1_5v>; + vdd2-supply = <&vdd2_3v3>; + vss2-supply = <&vss2_neg>; + }; + + - | + #include + + /* Configuration with external bypass amplifiers */ + amplifier { + compatible = "adi,adl8113"; + ctrl-gpios = <&gpio 24 GPIO_ACTIVE_HIGH>, + <&gpio 25 GPIO_ACTIVE_HIGH>; + vdd1-supply = <&vdd1_5v>; + vdd2-supply = <&vdd2_3v3>; + vss2-supply = <&vss2_neg>; + adi,external-bypass-a-gain-db = <20>; /* 20dB external amp on path A */ + adi,external-bypass-b-gain-db = <6>; /* 6dB external amp on path B */ + }; +... From b8c7340e2c623d1dc628421fd4c1a2d89b6ea694 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Fri, 12 Dec 2025 17:38:26 +0200 Subject: [PATCH 027/297] iio: amplifiers: adl8113: add driver support Add support for adl8113 10MHz to 12GHz Low Noise Amplifier with 10MHz to 14GHz bypass switches. Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- drivers/iio/amplifiers/Kconfig | 12 ++ drivers/iio/amplifiers/Makefile | 1 + drivers/iio/amplifiers/adl8113.c | 269 +++++++++++++++++++++++++++++++ 3 files changed, 282 insertions(+) create mode 100644 drivers/iio/amplifiers/adl8113.c diff --git a/drivers/iio/amplifiers/Kconfig b/drivers/iio/amplifiers/Kconfig index 55eb16b32f6c..a8a604863eed 100644 --- a/drivers/iio/amplifiers/Kconfig +++ b/drivers/iio/amplifiers/Kconfig @@ -36,6 +36,18 @@ config ADA4250 To compile this driver as a module, choose M here: the module will be called ada4250. +config ADL8113 + tristate "Analog Devices ADL8113 Low Noise Amplifier" + depends on GPIOLIB + help + Say yes here to build support for Analog Devices ADL8113 Low Noise + Amplifier with integrated bypass switches. The device supports four + operation modes controlled by GPIO pins: internal amplifier, + internal bypass, and two external bypass modes. + + To compile this driver as a module, choose M here: the + module will be called adl8113. + config HMC425 tristate "Analog Devices HMC425A and similar GPIO Gain Amplifiers" depends on GPIOLIB diff --git a/drivers/iio/amplifiers/Makefile b/drivers/iio/amplifiers/Makefile index 2126331129cf..0a76443be1aa 100644 --- a/drivers/iio/amplifiers/Makefile +++ b/drivers/iio/amplifiers/Makefile @@ -6,4 +6,5 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_AD8366) += ad8366.o obj-$(CONFIG_ADA4250) += ada4250.o +obj-$(CONFIG_ADL8113) += adl8113.o obj-$(CONFIG_HMC425) += hmc425a.o diff --git a/drivers/iio/amplifiers/adl8113.c b/drivers/iio/amplifiers/adl8113.c new file mode 100644 index 000000000000..b8a431b6616b --- /dev/null +++ b/drivers/iio/amplifiers/adl8113.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ADL8113 Low Noise Amplifier with integrated bypass switches + * + * Copyright 2025 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum adl8113_signal_path { + ADL8113_INTERNAL_AMP, + ADL8113_INTERNAL_BYPASS, + ADL8113_EXTERNAL_A, + ADL8113_EXTERNAL_B, +}; + +struct adl8113_gain_config { + enum adl8113_signal_path path; + int gain_db; +}; + +struct adl8113_state { + struct gpio_descs *gpios; + struct adl8113_gain_config *gain_configs; + unsigned int num_gain_configs; + enum adl8113_signal_path current_path; +}; + +static const char * const adl8113_supply_names[] = { + "vdd1", + "vss2", + "vdd2", +}; + +static int adl8113_set_path(struct adl8113_state *st, + enum adl8113_signal_path path) +{ + DECLARE_BITMAP(values, 2); + int ret; + + /* + * Determine GPIO values based on signal path. + * Va: bit 0, Vb: bit 1. + */ + switch (path) { + case ADL8113_INTERNAL_AMP: + bitmap_write(values, 0x00, 0, 2); + break; + case ADL8113_INTERNAL_BYPASS: + bitmap_write(values, 0x03, 0, 2); + break; + case ADL8113_EXTERNAL_A: + bitmap_write(values, 0x02, 0, 2); + break; + case ADL8113_EXTERNAL_B: + bitmap_write(values, 0x01, 0, 2); + break; + default: + return -EINVAL; + } + + ret = gpiod_set_array_value_cansleep(st->gpios->ndescs, st->gpios->desc, + st->gpios->info, values); + if (ret) + return ret; + + st->current_path = path; + return 0; +} + +static int adl8113_find_gain_config(struct adl8113_state *st, int gain_db) +{ + unsigned int i; + + for (i = 0; i < st->num_gain_configs; i++) { + if (st->gain_configs[i].gain_db == gain_db) + return i; + } + return -EINVAL; +} + +static const struct iio_chan_spec adl8113_channels[] = { + { + .type = IIO_VOLTAGE, + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_HARDWAREGAIN), + }, +}; + +static int adl8113_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct adl8113_state *st = iio_priv(indio_dev); + unsigned int i; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + /* Find current gain configuration */ + for (i = 0; i < st->num_gain_configs; i++) { + if (st->gain_configs[i].path == st->current_path) { + *val = st->gain_configs[i].gain_db; + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO_DB; + } + } + return -EINVAL; + default: + return -EINVAL; + } +} + +static int adl8113_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct adl8113_state *st = iio_priv(indio_dev); + int config_idx; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + if (val2 != 0) + return -EINVAL; + + config_idx = adl8113_find_gain_config(st, val); + if (config_idx < 0) + return config_idx; + + return adl8113_set_path(st, st->gain_configs[config_idx].path); + default: + return -EINVAL; + } +} + +static const struct iio_info adl8113_info = { + .read_raw = adl8113_read_raw, + .write_raw = adl8113_write_raw, +}; + +static int adl8113_init_gain_configs(struct device *dev, struct adl8113_state *st) +{ + int external_a_gain, external_b_gain; + unsigned int i; + + /* + * Allocate for all 4 possible paths: + * - Internal amp and bypass (always present) + * - External bypass A and B (optional if configured) + */ + st->gain_configs = devm_kcalloc(dev, 4, sizeof(*st->gain_configs), + GFP_KERNEL); + if (!st->gain_configs) + return -ENOMEM; + + /* Start filling the gain configurations with data */ + i = 0; + + /* Always include internal amplifier (14dB) */ + st->gain_configs[i++] = (struct adl8113_gain_config) { + .path = ADL8113_INTERNAL_AMP, + .gain_db = 14, + }; + + /* Always include internal bypass (-2dB insertion loss) */ + st->gain_configs[i++] = (struct adl8113_gain_config) { + .path = ADL8113_INTERNAL_BYPASS, + .gain_db = -2, + }; + + /* Add external bypass A if configured */ + if (!device_property_read_u32(dev, "adi,external-bypass-a-gain-db", + &external_a_gain)) { + st->gain_configs[i++] = (struct adl8113_gain_config) { + .path = ADL8113_EXTERNAL_A, + .gain_db = external_a_gain, + }; + } + + /* Add external bypass B if configured */ + if (!device_property_read_u32(dev, "adi,external-bypass-b-gain-db", + &external_b_gain)) { + st->gain_configs[i++] = (struct adl8113_gain_config) { + .path = ADL8113_EXTERNAL_B, + .gain_db = external_b_gain, + }; + } + + st->num_gain_configs = i; + + return 0; +} + +static int adl8113_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct adl8113_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->gpios = devm_gpiod_get_array(dev, "ctrl", GPIOD_OUT_LOW); + if (IS_ERR(st->gpios)) + return dev_err_probe(dev, PTR_ERR(st->gpios), + "failed to get control GPIOs\n"); + + if (st->gpios->ndescs != 2) + return dev_err_probe(dev, -EINVAL, + "expected 2 control GPIOs, got %u\n", + st->gpios->ndescs); + + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(adl8113_supply_names), + adl8113_supply_names); + if (ret) + return dev_err_probe(dev, ret, + "failed to get and enable supplies\n"); + + /* Initialize gain configurations from devicetree */ + ret = adl8113_init_gain_configs(dev, st); + if (ret) + return ret; + + /* Initialize to internal amplifier path (14dB) */ + ret = adl8113_set_path(st, ADL8113_INTERNAL_AMP); + if (ret) + return ret; + + indio_dev->info = &adl8113_info; + indio_dev->name = "adl8113"; + indio_dev->channels = adl8113_channels; + indio_dev->num_channels = ARRAY_SIZE(adl8113_channels); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id adl8113_of_match[] = { + { .compatible = "adi,adl8113" }, + { } +}; +MODULE_DEVICE_TABLE(of, adl8113_of_match); + +static struct platform_driver adl8113_driver = { + .driver = { + .name = "adl8113", + .of_match_table = adl8113_of_match, + }, + .probe = adl8113_probe, +}; +module_platform_driver(adl8113_driver); + +MODULE_AUTHOR("Antoniu Miclaus "); +MODULE_DESCRIPTION("Analog Devices ADL8113 Low Noise Amplifier"); +MODULE_LICENSE("GPL"); From 6fa9eb81f32a50fe1953e1b9ab839e7467c94274 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Thu, 11 Dec 2025 17:45:56 +0900 Subject: [PATCH 028/297] dt-bindings: iio: adc: Allow interrupts property for AST2600 The device has interrupts allocated according to the datasheet, and the devicetree already defines the interrupt property. Address existing warnings by allowing the property. Signed-off-by: Andrew Jeffery Acked-by: Rob Herring (Arm) Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/adc/aspeed,ast2600-adc.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/adc/aspeed,ast2600-adc.yaml b/Documentation/devicetree/bindings/iio/adc/aspeed,ast2600-adc.yaml index 509bfb1007c4..249101b55cf4 100644 --- a/Documentation/devicetree/bindings/iio/adc/aspeed,ast2600-adc.yaml +++ b/Documentation/devicetree/bindings/iio/adc/aspeed,ast2600-adc.yaml @@ -44,6 +44,9 @@ properties: Input clock used to derive the sample clock. Expected to be the SoC's APB clock. + interrupts: + maxItems: 1 + resets: maxItems: 1 From 34744a6ddf63316db707d090737117c212ce136b Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Tue, 9 Dec 2025 15:38:10 +0000 Subject: [PATCH 029/297] dt-bindings: adc: ad9467: add support for ad9211 This device has e.g. different scaling values than currently listed devices. Acked-by: Krzysztof Kozlowski Signed-off-by: Tomas Melin Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/adc/adi,ad9467.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad9467.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad9467.yaml index 2606c0c5dfc6..5acfb0eef4d5 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad9467.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad9467.yaml @@ -18,6 +18,7 @@ description: | All the parts support the register map described by Application Note AN-877 https://www.analog.com/media/en/technical-documentation/application-notes/AN-877.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/AD9211.pdf https://www.analog.com/media/en/technical-documentation/data-sheets/AD9265.pdf https://www.analog.com/media/en/technical-documentation/data-sheets/AD9434.pdf https://www.analog.com/media/en/technical-documentation/data-sheets/AD9467.pdf @@ -25,6 +26,7 @@ description: | properties: compatible: enum: + - adi,ad9211 - adi,ad9265 - adi,ad9434 - adi,ad9467 From 1f0b6415b642b28811c8fe295e85c2a550bf05d1 Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Tue, 9 Dec 2025 15:38:11 +0000 Subject: [PATCH 030/297] iio: adc: ad9467: sort header includes Include headers in ascending order. Signed-off-by: Tomas Melin Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad9467.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/drivers/iio/adc/ad9467.c b/drivers/iio/adc/ad9467.c index 995d811d4fbe..02610ffb06c8 100644 --- a/drivers/iio/adc/ad9467.c +++ b/drivers/iio/adc/ad9467.c @@ -8,26 +8,24 @@ #include #include #include +#include #include +#include +#include +#include +#include +#include #include #include -#include -#include +#include +#include #include #include -#include -#include -#include -#include -#include - #include #include #include -#include - /* * ADI High-Speed ADC common spi interface registers * See Application-Note AN-877: From 77a017410b5c59d549fefa76b2fb62173de7b30c Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Tue, 9 Dec 2025 15:38:12 +0000 Subject: [PATCH 031/297] iio: adc: ad9467: add support for ad9211 The AD9211 is a 10-bit monolithic sampling analog-to-digital converter optimized for high performance, low power, and ease of use. The product operates at up to a 300 MSPS conversion rate and is optimized for outstanding dynamic performance in wideband carrier and broadband systems. The scale table implemented here is not an exact match with the datasheet as the table presented there is missing some information. The reference presents these values as being linear, but that does not add up. There is information missing in the table. Implemented scale table matches values at the middle and at the ends, smoothing the curve towards middle and end. Impact on end result from deviation in scale factor affects only software using it for scaling. All the possible hw-settings are also available with this implementation. Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/AD9211.pdf Reviewed-by: Andy Shevchenko Signed-off-by: Tomas Melin Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad9467.c | 43 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/drivers/iio/adc/ad9467.c b/drivers/iio/adc/ad9467.c index 02610ffb06c8..59c3fa3bcc9b 100644 --- a/drivers/iio/adc/ad9467.c +++ b/drivers/iio/adc/ad9467.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -79,6 +80,14 @@ /* AN877_ADC_REG_OUTPUT_DELAY */ #define AN877_ADC_DCO_DELAY_ENABLE 0x80 +/* + * Analog Devices AD9211 10-Bit, 200/250/300 MSPS ADC + */ + +#define CHIPID_AD9211 0x06 +#define AD9211_DEF_OUTPUT_MODE 0x00 +#define AD9211_REG_VREF_MASK GENMASK(4, 0) + /* * Analog Devices AD9265 16-Bit, 125/105/80 MSPS ADC */ @@ -237,6 +246,17 @@ static const int ad9434_offset_range[] = { -128, 1, 127, }; +static const unsigned int ad9211_scale_table[][2] = { + {980, 0x10}, {1000, 0x11}, {1020, 0x12}, {1040, 0x13}, + {1060, 0x14}, {1080, 0x15}, {1100, 0x16}, {1120, 0x17}, + {1140, 0x18}, {1160, 0x19}, {1180, 0x1A}, {1190, 0x1B}, + {1200, 0x1C}, {1210, 0x1D}, {1220, 0x1E}, {1230, 0x1F}, + {1250, 0x0}, {1270, 0x1}, {1290, 0x2}, {1310, 0x3}, + {1330, 0x4}, {1350, 0x5}, {1370, 0x6}, {1390, 0x7}, + {1410, 0x8}, {1430, 0x9}, {1450, 0xA}, {1460, 0xB}, + {1470, 0xC}, {1480, 0xD}, {1490, 0xE}, {1500, 0xF}, +}; + static const unsigned int ad9265_scale_table[][2] = { {1250, 0x00}, {1500, 0x40}, {1750, 0x80}, {2000, 0xC0}, }; @@ -300,6 +320,10 @@ static void __ad9467_get_scale(struct ad9467_state *st, int index, }, \ } +static const struct iio_chan_spec ad9211_channels[] = { + AD9467_CHAN(0, BIT(IIO_CHAN_INFO_SCALE), 0, 10, 's'), +}; + static const struct iio_chan_spec ad9434_channels[] = { { .type = IIO_VOLTAGE, @@ -390,6 +414,23 @@ static const struct ad9467_chip_info ad9434_chip_tbl = { .offset_range = ad9434_offset_range, }; +static const struct ad9467_chip_info ad9211_chip_tbl = { + .name = "ad9211", + .id = CHIPID_AD9211, + .max_rate = 300 * HZ_PER_MHZ, + .scale_table = ad9211_scale_table, + .num_scales = ARRAY_SIZE(ad9211_scale_table), + .channels = ad9211_channels, + .num_channels = ARRAY_SIZE(ad9211_channels), + .test_points = AD9647_MAX_TEST_POINTS, + .test_mask = GENMASK(AN877_ADC_TESTMODE_ONE_ZERO_TOGGLE, + AN877_ADC_TESTMODE_OFF), + .test_mask_len = AN877_ADC_TESTMODE_ONE_ZERO_TOGGLE + 1, + .default_output_mode = AD9211_DEF_OUTPUT_MODE, + .vref_mask = AD9211_REG_VREF_MASK, + .has_dco = true, +}; + static const struct ad9467_chip_info ad9265_chip_tbl = { .name = "ad9265", .id = CHIPID_AD9265, @@ -1320,6 +1361,7 @@ static int ad9467_probe(struct spi_device *spi) } static const struct of_device_id ad9467_of_match[] = { + { .compatible = "adi,ad9211", .data = &ad9211_chip_tbl, }, { .compatible = "adi,ad9265", .data = &ad9265_chip_tbl, }, { .compatible = "adi,ad9434", .data = &ad9434_chip_tbl, }, { .compatible = "adi,ad9467", .data = &ad9467_chip_tbl, }, @@ -1331,6 +1373,7 @@ static const struct of_device_id ad9467_of_match[] = { MODULE_DEVICE_TABLE(of, ad9467_of_match); static const struct spi_device_id ad9467_ids[] = { + { "ad9211", (kernel_ulong_t)&ad9211_chip_tbl }, { "ad9265", (kernel_ulong_t)&ad9265_chip_tbl }, { "ad9434", (kernel_ulong_t)&ad9434_chip_tbl }, { "ad9467", (kernel_ulong_t)&ad9467_chip_tbl }, From b2192756759308f49fbca435b5a0b9a7de7054a9 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 7 Dec 2025 08:00:47 +0200 Subject: [PATCH 032/297] dt-bindings: iio: pressure: add honeywell,abp2030pa Adds binding for digital Honeywell ABP2 series pressure and temperature sensors. The i2c address is hardcoded and depends on the part number. There is an optional interrupt that signals the end of conversion. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- .../iio/pressure/honeywell,abp2030pa.yaml | 132 ++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 138 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/pressure/honeywell,abp2030pa.yaml diff --git a/Documentation/devicetree/bindings/iio/pressure/honeywell,abp2030pa.yaml b/Documentation/devicetree/bindings/iio/pressure/honeywell,abp2030pa.yaml new file mode 100644 index 000000000000..e82897ffac3b --- /dev/null +++ b/Documentation/devicetree/bindings/iio/pressure/honeywell,abp2030pa.yaml @@ -0,0 +1,132 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/pressure/honeywell,abp2030pa.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Honeywell abp2030pa pressure sensor + +maintainers: + - Petre Rodan + +description: | + Honeywell pressure sensor of model abp2030pa. + + This sensor has an I2C and SPI interface. + + There are many models with different pressure ranges available. The vendor + calls them "ABP2 series". All of them have an identical programming model and + differ in the pressure range and measurement unit. + + To support different models one needs to specify its pressure triplet. + + For custom silicon chips not covered by the Honeywell ABP2 series datasheet, + the pressure values can be specified manually via honeywell,pmin-pascal and + honeywell,pmax-pascal. + + Specifications about the devices can be found at: + https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/basic-abp2-series/documents/sps-siot-abp2-series-datasheet-32350268-en.pdf + +properties: + compatible: + const: honeywell,abp2030pa + + reg: + maxItems: 1 + + interrupts: + description: + Optional interrupt for indicating end of conversion. + SPI variants of ABP2 chips do not provide this feature. + maxItems: 1 + + honeywell,pressure-triplet: + description: | + Case-sensitive five character string that defines pressure range, unit + and type as part of the device nomenclature. In the unlikely case of a + custom chip, unset and provide pmin-pascal and pmax-pascal instead. + enum: [001BA, 1.6BA, 2.5BA, 004BA, 006BA, 008BA, 010BA, 012BA, 001BD, + 1.6BD, 2.5BD, 004BD, 001BG, 1.6BG, 2.5BG, 004BG, 006BG, 008BG, + 010BG, 012BG, 001GG, 1.2GG, 100KA, 160KA, 250KA, 001KD, 1.6KD, + 2.5KD, 004KD, 006KD, 010KD, 016KD, 025KD, 040KD, 060KD, 100KD, + 160KD, 250KD, 400KD, 001KG, 1.6KG, 2.5KG, 004KG, 006KG, 010KG, + 016KG, 025KG, 040KG, 060KG, 100KG, 160KG, 250KG, 400KG, 600KG, + 800KG, 250LD, 600LD, 600LG, 2.5MD, 006MD, 010MD, 016MD, 025MD, + 040MD, 060MD, 100MD, 160MD, 250MD, 400MD, 600MD, 006MG, 010MG, + 016MG, 025MG, 040MG, 060MG, 100MG, 160MG, 250MG, 400MG, 600MG, + 001ND, 002ND, 004ND, 005ND, 010ND, 020ND, 030ND, 002NG, 004NG, + 005NG, 010NG, 020NG, 030NG, 015PA, 030PA, 060PA, 100PA, 150PA, + 175PA, 001PD, 005PD, 015PD, 030PD, 060PD, 001PG, 005PG, 015PG, + 030PG, 060PG, 100PG, 150PG, 175PG] + $ref: /schemas/types.yaml#/definitions/string + + honeywell,pmin-pascal: + description: + Minimum pressure value the sensor can measure in pascal. + + honeywell,pmax-pascal: + description: + Maximum pressure value the sensor can measure in pascal. + + spi-max-frequency: + maximum: 800000 + + vdd-supply: true + +required: + - compatible + - reg + - vdd-supply + +oneOf: + - required: + - honeywell,pressure-triplet + - required: + - honeywell,pmin-pascal + - honeywell,pmax-pascal + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml + - if: + required: + - honeywell,pressure-triplet + then: + properties: + honeywell,pmin-pascal: false + honeywell,pmax-pascal: false + +additionalProperties: false + +examples: + - | + #include + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + pressure@18 { + compatible = "honeywell,abp2030pa"; + reg = <0x18>; + interrupt-parent = <&gpio3>; + interrupts = <21 IRQ_TYPE_EDGE_RISING>; + + honeywell,pressure-triplet = "001BA"; + vdd-supply = <&vcc_3v3>; + }; + }; + - | + spi { + #address-cells = <1>; + #size-cells = <0>; + + pressure@0 { + compatible = "honeywell,abp2030pa"; + reg = <0>; + spi-max-frequency = <800000>; + + honeywell,pressure-triplet = "001PD"; + vdd-supply = <&vcc_3v3>; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 5b11839cba9d..91e37589fde2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11505,6 +11505,12 @@ F: lib/test_hmm* F: mm/hmm* F: tools/testing/selftests/mm/*hmm* +HONEYWELL ABP2030PA PRESSURE SENSOR SERIES IIO DRIVER +M: Petre Rodan +L: linux-iio@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/iio/pressure/honeywell,abp2030pa.yaml + HONEYWELL HSC030PA PRESSURE SENSOR SERIES IIO DRIVER M: Petre Rodan L: linux-iio@vger.kernel.org From 47d323ce1e8934be36ec475b3bed7ad90d15d35d Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 7 Dec 2025 08:00:48 +0200 Subject: [PATCH 033/297] iio: pressure: add Honeywell ABP2 driver Adds driver for digital Honeywell ABP2 series of board mount pressure and temperature sensors. This driver covers 113 different pressure ranges and units on both i2c and SPI buses. The communication protocol involves sending two simple commands to the sensor and there is no register access or a memory map. For this reason the regmap API was not used. The i2c address is hardcoded and depends on the part number. Optional end of conversion interrupt control is present on the i2c variants of the chips. The EOC can also be defined for the SPI variants if a non-ABP2 but compatible chip is to be driven. Tested on two sensors (ABP2MRRT001PDSA3 and ABP2DANT001BA2A3). ocuments/sps-siot-abp2-series-datasheet-32350268-en.pdf Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/basic-abp2-series/d Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/pressure/Kconfig | 29 ++ drivers/iio/pressure/Makefile | 3 + drivers/iio/pressure/abp2030pa.c | 544 +++++++++++++++++++++++++++ drivers/iio/pressure/abp2030pa.h | 73 ++++ drivers/iio/pressure/abp2030pa_i2c.c | 90 +++++ drivers/iio/pressure/abp2030pa_spi.c | 67 ++++ 7 files changed, 807 insertions(+) create mode 100644 drivers/iio/pressure/abp2030pa.c create mode 100644 drivers/iio/pressure/abp2030pa.h create mode 100644 drivers/iio/pressure/abp2030pa_i2c.c create mode 100644 drivers/iio/pressure/abp2030pa_spi.c diff --git a/MAINTAINERS b/MAINTAINERS index 91e37589fde2..dc6936ba0ab9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11510,6 +11510,7 @@ M: Petre Rodan L: linux-iio@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/iio/pressure/honeywell,abp2030pa.yaml +F: drivers/iio/pressure/abp2030pa* HONEYWELL HSC030PA PRESSURE SENSOR SERIES IIO DRIVER M: Petre Rodan diff --git a/drivers/iio/pressure/Kconfig b/drivers/iio/pressure/Kconfig index 2fe9dc90cceb..b2720be51506 100644 --- a/drivers/iio/pressure/Kconfig +++ b/drivers/iio/pressure/Kconfig @@ -16,6 +16,35 @@ config ABP060MG To compile this driver as a module, choose M here: the module will be called abp060mg. +config ABP2030PA + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config ABP2030PA_I2C + tristate "Honeywell ABP2 pressure sensor series I2C driver" + depends on I2C + select ABP2030PA + help + Say Y here to build I2C bus support for the Honeywell ABP2 + series pressure and temperature digital sensor. + + To compile this driver as a module, choose M here: the module + will be called abp2030pa_i2c and you will also get abp2030pa + for the core module. + +config ABP2030PA_SPI + tristate "Honeywell ABP2 pressure sensor series SPI driver" + depends on SPI_MASTER + select ABP2030PA + help + Say Y here to build I2C bus support for the Honeywell ABP2 + series pressure and temperature digital sensor. + + To compile this driver as a module, choose M here: the module + will be called abp2030pa_spi and you will also get abp2030pa + for the core module. + config ROHM_BM1390 tristate "ROHM BM1390GLV-Z pressure sensor driver" depends on I2C diff --git a/drivers/iio/pressure/Makefile b/drivers/iio/pressure/Makefile index a21443e992b9..bc0d11a20acc 100644 --- a/drivers/iio/pressure/Makefile +++ b/drivers/iio/pressure/Makefile @@ -5,6 +5,9 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_ABP060MG) += abp060mg.o +obj-$(CONFIG_ABP2030PA) += abp2030pa.o +obj-$(CONFIG_ABP2030PA_I2C) += abp2030pa_i2c.o +obj-$(CONFIG_ABP2030PA_SPI) += abp2030pa_spi.o obj-$(CONFIG_ADP810) += adp810.o obj-$(CONFIG_ROHM_BM1390) += rohm-bm1390.o obj-$(CONFIG_BMP280) += bmp280.o diff --git a/drivers/iio/pressure/abp2030pa.c b/drivers/iio/pressure/abp2030pa.c new file mode 100644 index 000000000000..be11e28208ec --- /dev/null +++ b/drivers/iio/pressure/abp2030pa.c @@ -0,0 +1,544 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Honeywell ABP2 series pressure sensor driver + * + * Copyright (c) 2025 Petre Rodan + * + * Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/basic-abp2-series/documents/sps-siot-abp2-series-datasheet-32350268-en.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "abp2030pa.h" + +/* Status byte flags */ +#define ABP2_ST_POWER BIT(6) /* 1 if device is powered */ +#define ABP2_ST_BUSY BIT(5) /* 1 if device is busy */ + +#define ABP2_CMD_NOP 0xf0 +#define ABP2_CMD_SYNC 0xaa +#define ABP2_PKT_SYNC_LEN 3 +#define ABP2_PKT_NOP_LEN ABP2_MEASUREMENT_RD_SIZE + +struct abp2_func_spec { + u32 output_min; + u32 output_max; +}; + +/* transfer function A: 10% to 90% of 2^24 */ +static const struct abp2_func_spec abp2_func_spec[] = { + [ABP2_FUNCTION_A] = { .output_min = 1677722, .output_max = 15099494 }, +}; + +enum abp2_variants { + ABP2001BA, ABP21_6BA, ABP22_5BA, ABP2004BA, ABP2006BA, ABP2008BA, + ABP2010BA, ABP2012BA, ABP2001BD, ABP21_6BD, ABP22_5BD, ABP2004BD, + ABP2001BG, ABP21_6BG, ABP22_5BG, ABP2004BG, ABP2006BG, ABP2008BG, + ABP2010BG, ABP2012BG, ABP2001GG, ABP21_2GG, ABP2100KA, ABP2160KA, + ABP2250KA, ABP2001KD, ABP21_6KD, ABP22_5KD, ABP2004KD, ABP2006KD, + ABP2010KD, ABP2016KD, ABP2025KD, ABP2040KD, ABP2060KD, ABP2100KD, + ABP2160KD, ABP2250KD, ABP2400KD, ABP2001KG, ABP21_6KG, ABP22_5KG, + ABP2004KG, ABP2006KG, ABP2010KG, ABP2016KG, ABP2025KG, ABP2040KG, + ABP2060KG, ABP2100KG, ABP2160KG, ABP2250KG, ABP2400KG, ABP2600KG, + ABP2800KG, ABP2250LD, ABP2600LD, ABP2600LG, ABP22_5MD, ABP2006MD, + ABP2010MD, ABP2016MD, ABP2025MD, ABP2040MD, ABP2060MD, ABP2100MD, + ABP2160MD, ABP2250MD, ABP2400MD, ABP2600MD, ABP2006MG, ABP2010MG, + ABP2016MG, ABP2025MG, ABP2040MG, ABP2060MG, ABP2100MG, ABP2160MG, + ABP2250MG, ABP2400MG, ABP2600MG, ABP2001ND, ABP2002ND, ABP2004ND, + ABP2005ND, ABP2010ND, ABP2020ND, ABP2030ND, ABP2002NG, ABP2004NG, + ABP2005NG, ABP2010NG, ABP2020NG, ABP2030NG, ABP2015PA, ABP2030PA, + ABP2060PA, ABP2100PA, ABP2150PA, ABP2175PA, ABP2001PD, ABP2005PD, + ABP2015PD, ABP2030PD, ABP2060PD, ABP2001PG, ABP2005PG, ABP2015PG, + ABP2030PG, ABP2060PG, ABP2100PG, ABP2150PG, ABP2175PG, +}; + +static const char * const abp2_triplet_variants[] = { + [ABP2001BA] = "001BA", [ABP21_6BA] = "1.6BA", [ABP22_5BA] = "2.5BA", + [ABP2004BA] = "004BA", [ABP2006BA] = "006BA", [ABP2008BA] = "008BA", + [ABP2010BA] = "010BA", [ABP2012BA] = "012BA", [ABP2001BD] = "001BD", + [ABP21_6BD] = "1.6BD", [ABP22_5BD] = "2.5BD", [ABP2004BD] = "004BD", + [ABP2001BG] = "001BG", [ABP21_6BG] = "1.6BG", [ABP22_5BG] = "2.5BG", + [ABP2004BG] = "004BG", [ABP2006BG] = "006BG", [ABP2008BG] = "008BG", + [ABP2010BG] = "010BG", [ABP2012BG] = "012BG", [ABP2001GG] = "001GG", + [ABP21_2GG] = "1.2GG", [ABP2100KA] = "100KA", [ABP2160KA] = "160KA", + [ABP2250KA] = "250KA", [ABP2001KD] = "001KD", [ABP21_6KD] = "1.6KD", + [ABP22_5KD] = "2.5KD", [ABP2004KD] = "004KD", [ABP2006KD] = "006KD", + [ABP2010KD] = "010KD", [ABP2016KD] = "016KD", [ABP2025KD] = "025KD", + [ABP2040KD] = "040KD", [ABP2060KD] = "060KD", [ABP2100KD] = "100KD", + [ABP2160KD] = "160KD", [ABP2250KD] = "250KD", [ABP2400KD] = "400KD", + [ABP2001KG] = "001KG", [ABP21_6KG] = "1.6KG", [ABP22_5KG] = "2.5KG", + [ABP2004KG] = "004KG", [ABP2006KG] = "006KG", [ABP2010KG] = "010KG", + [ABP2016KG] = "016KG", [ABP2025KG] = "025KG", [ABP2040KG] = "040KG", + [ABP2060KG] = "060KG", [ABP2100KG] = "100KG", [ABP2160KG] = "160KG", + [ABP2250KG] = "250KG", [ABP2400KG] = "400KG", [ABP2600KG] = "600KG", + [ABP2800KG] = "800KG", [ABP2250LD] = "250LD", [ABP2600LD] = "600LD", + [ABP2600LG] = "600LG", [ABP22_5MD] = "2.5MD", [ABP2006MD] = "006MD", + [ABP2010MD] = "010MD", [ABP2016MD] = "016MD", [ABP2025MD] = "025MD", + [ABP2040MD] = "040MD", [ABP2060MD] = "060MD", [ABP2100MD] = "100MD", + [ABP2160MD] = "160MD", [ABP2250MD] = "250MD", [ABP2400MD] = "400MD", + [ABP2600MD] = "600MD", [ABP2006MG] = "006MG", [ABP2010MG] = "010MG", + [ABP2016MG] = "016MG", [ABP2025MG] = "025MG", [ABP2040MG] = "040MG", + [ABP2060MG] = "060MG", [ABP2100MG] = "100MG", [ABP2160MG] = "160MG", + [ABP2250MG] = "250MG", [ABP2400MG] = "400MG", [ABP2600MG] = "600MG", + [ABP2001ND] = "001ND", [ABP2002ND] = "002ND", [ABP2004ND] = "004ND", + [ABP2005ND] = "005ND", [ABP2010ND] = "010ND", [ABP2020ND] = "020ND", + [ABP2030ND] = "030ND", [ABP2002NG] = "002NG", [ABP2004NG] = "004NG", + [ABP2005NG] = "005NG", [ABP2010NG] = "010NG", [ABP2020NG] = "020NG", + [ABP2030NG] = "030NG", [ABP2015PA] = "015PA", [ABP2030PA] = "030PA", + [ABP2060PA] = "060PA", [ABP2100PA] = "100PA", [ABP2150PA] = "150PA", + [ABP2175PA] = "175PA", [ABP2001PD] = "001PD", [ABP2005PD] = "005PD", + [ABP2015PD] = "015PD", [ABP2030PD] = "030PD", [ABP2060PD] = "060PD", + [ABP2001PG] = "001PG", [ABP2005PG] = "005PG", [ABP2015PG] = "015PG", + [ABP2030PG] = "030PG", [ABP2060PG] = "060PG", [ABP2100PG] = "100PG", + [ABP2150PG] = "150PG", [ABP2175PG] = "175PG", +}; + +/** + * struct abp2_range_config - list of pressure ranges based on nomenclature + * @pmin: lowest pressure that can be measured + * @pmax: highest pressure that can be measured + */ +struct abp2_range_config { + s32 pmin; + s32 pmax; +}; + +/* All min max limits have been converted to pascals */ +static const struct abp2_range_config abp2_range_config[] = { + [ABP2001BA] = { .pmin = 0, .pmax = 100000 }, + [ABP21_6BA] = { .pmin = 0, .pmax = 160000 }, + [ABP22_5BA] = { .pmin = 0, .pmax = 250000 }, + [ABP2004BA] = { .pmin = 0, .pmax = 400000 }, + [ABP2006BA] = { .pmin = 0, .pmax = 600000 }, + [ABP2008BA] = { .pmin = 0, .pmax = 800000 }, + [ABP2010BA] = { .pmin = 0, .pmax = 1000000 }, + [ABP2012BA] = { .pmin = 0, .pmax = 1200000 }, + [ABP2001BD] = { .pmin = -100000, .pmax = 100000 }, + [ABP21_6BD] = { .pmin = -160000, .pmax = 160000 }, + [ABP22_5BD] = { .pmin = -250000, .pmax = 250000 }, + [ABP2004BD] = { .pmin = -400000, .pmax = 400000 }, + [ABP2001BG] = { .pmin = 0, .pmax = 100000 }, + [ABP21_6BG] = { .pmin = 0, .pmax = 160000 }, + [ABP22_5BG] = { .pmin = 0, .pmax = 250000 }, + [ABP2004BG] = { .pmin = 0, .pmax = 400000 }, + [ABP2006BG] = { .pmin = 0, .pmax = 600000 }, + [ABP2008BG] = { .pmin = 0, .pmax = 800000 }, + [ABP2010BG] = { .pmin = 0, .pmax = 1000000 }, + [ABP2012BG] = { .pmin = 0, .pmax = 1200000 }, + [ABP2001GG] = { .pmin = 0, .pmax = 1000000 }, + [ABP21_2GG] = { .pmin = 0, .pmax = 1200000 }, + [ABP2100KA] = { .pmin = 0, .pmax = 100000 }, + [ABP2160KA] = { .pmin = 0, .pmax = 160000 }, + [ABP2250KA] = { .pmin = 0, .pmax = 250000 }, + [ABP2001KD] = { .pmin = -1000, .pmax = 1000 }, + [ABP21_6KD] = { .pmin = -1600, .pmax = 1600 }, + [ABP22_5KD] = { .pmin = -2500, .pmax = 2500 }, + [ABP2004KD] = { .pmin = -4000, .pmax = 4000 }, + [ABP2006KD] = { .pmin = -6000, .pmax = 6000 }, + [ABP2010KD] = { .pmin = -10000, .pmax = 10000 }, + [ABP2016KD] = { .pmin = -16000, .pmax = 16000 }, + [ABP2025KD] = { .pmin = -25000, .pmax = 25000 }, + [ABP2040KD] = { .pmin = -40000, .pmax = 40000 }, + [ABP2060KD] = { .pmin = -60000, .pmax = 60000 }, + [ABP2100KD] = { .pmin = -100000, .pmax = 100000 }, + [ABP2160KD] = { .pmin = -160000, .pmax = 160000 }, + [ABP2250KD] = { .pmin = -250000, .pmax = 250000 }, + [ABP2400KD] = { .pmin = -400000, .pmax = 400000 }, + [ABP2001KG] = { .pmin = 0, .pmax = 1000 }, + [ABP21_6KG] = { .pmin = 0, .pmax = 1600 }, + [ABP22_5KG] = { .pmin = 0, .pmax = 2500 }, + [ABP2004KG] = { .pmin = 0, .pmax = 4000 }, + [ABP2006KG] = { .pmin = 0, .pmax = 6000 }, + [ABP2010KG] = { .pmin = 0, .pmax = 10000 }, + [ABP2016KG] = { .pmin = 0, .pmax = 16000 }, + [ABP2025KG] = { .pmin = 0, .pmax = 25000 }, + [ABP2040KG] = { .pmin = 0, .pmax = 40000 }, + [ABP2060KG] = { .pmin = 0, .pmax = 60000 }, + [ABP2100KG] = { .pmin = 0, .pmax = 100000 }, + [ABP2160KG] = { .pmin = 0, .pmax = 160000 }, + [ABP2250KG] = { .pmin = 0, .pmax = 250000 }, + [ABP2400KG] = { .pmin = 0, .pmax = 400000 }, + [ABP2600KG] = { .pmin = 0, .pmax = 600000 }, + [ABP2800KG] = { .pmin = 0, .pmax = 800000 }, + [ABP2250LD] = { .pmin = -250, .pmax = 250 }, + [ABP2600LD] = { .pmin = -600, .pmax = 600 }, + [ABP2600LG] = { .pmin = 0, .pmax = 600 }, + [ABP22_5MD] = { .pmin = -250, .pmax = 250 }, + [ABP2006MD] = { .pmin = -600, .pmax = 600 }, + [ABP2010MD] = { .pmin = -1000, .pmax = 1000 }, + [ABP2016MD] = { .pmin = -1600, .pmax = 1600 }, + [ABP2025MD] = { .pmin = -2500, .pmax = 2500 }, + [ABP2040MD] = { .pmin = -4000, .pmax = 4000 }, + [ABP2060MD] = { .pmin = -6000, .pmax = 6000 }, + [ABP2100MD] = { .pmin = -10000, .pmax = 10000 }, + [ABP2160MD] = { .pmin = -16000, .pmax = 16000 }, + [ABP2250MD] = { .pmin = -25000, .pmax = 25000 }, + [ABP2400MD] = { .pmin = -40000, .pmax = 40000 }, + [ABP2600MD] = { .pmin = -60000, .pmax = 60000 }, + [ABP2006MG] = { .pmin = 0, .pmax = 600 }, + [ABP2010MG] = { .pmin = 0, .pmax = 1000 }, + [ABP2016MG] = { .pmin = 0, .pmax = 1600 }, + [ABP2025MG] = { .pmin = 0, .pmax = 2500 }, + [ABP2040MG] = { .pmin = 0, .pmax = 4000 }, + [ABP2060MG] = { .pmin = 0, .pmax = 6000 }, + [ABP2100MG] = { .pmin = 0, .pmax = 10000 }, + [ABP2160MG] = { .pmin = 0, .pmax = 16000 }, + [ABP2250MG] = { .pmin = 0, .pmax = 25000 }, + [ABP2400MG] = { .pmin = 0, .pmax = 40000 }, + [ABP2600MG] = { .pmin = 0, .pmax = 60000 }, + [ABP2001ND] = { .pmin = -249, .pmax = 249 }, + [ABP2002ND] = { .pmin = -498, .pmax = 498 }, + [ABP2004ND] = { .pmin = -996, .pmax = 996 }, + [ABP2005ND] = { .pmin = -1245, .pmax = 1245 }, + [ABP2010ND] = { .pmin = -2491, .pmax = 2491 }, + [ABP2020ND] = { .pmin = -4982, .pmax = 4982 }, + [ABP2030ND] = { .pmin = -7473, .pmax = 7473 }, + [ABP2002NG] = { .pmin = 0, .pmax = 498 }, + [ABP2004NG] = { .pmin = 0, .pmax = 996 }, + [ABP2005NG] = { .pmin = 0, .pmax = 1245 }, + [ABP2010NG] = { .pmin = 0, .pmax = 2491 }, + [ABP2020NG] = { .pmin = 0, .pmax = 4982 }, + [ABP2030NG] = { .pmin = 0, .pmax = 7473 }, + [ABP2015PA] = { .pmin = 0, .pmax = 103421 }, + [ABP2030PA] = { .pmin = 0, .pmax = 206843 }, + [ABP2060PA] = { .pmin = 0, .pmax = 413685 }, + [ABP2100PA] = { .pmin = 0, .pmax = 689476 }, + [ABP2150PA] = { .pmin = 0, .pmax = 1034214 }, + [ABP2175PA] = { .pmin = 0, .pmax = 1206583 }, + [ABP2001PD] = { .pmin = -6895, .pmax = 6895 }, + [ABP2005PD] = { .pmin = -34474, .pmax = 34474 }, + [ABP2015PD] = { .pmin = -103421, .pmax = 103421 }, + [ABP2030PD] = { .pmin = -206843, .pmax = 206843 }, + [ABP2060PD] = { .pmin = -413685, .pmax = 413685 }, + [ABP2001PG] = { .pmin = 0, .pmax = 6895 }, + [ABP2005PG] = { .pmin = 0, .pmax = 34474 }, + [ABP2015PG] = { .pmin = 0, .pmax = 103421 }, + [ABP2030PG] = { .pmin = 0, .pmax = 206843 }, + [ABP2060PG] = { .pmin = 0, .pmax = 413685 }, + [ABP2100PG] = { .pmin = 0, .pmax = 689476 }, + [ABP2150PG] = { .pmin = 0, .pmax = 1034214 }, + [ABP2175PG] = { .pmin = 0, .pmax = 1206583 }, +}; + +static_assert(ARRAY_SIZE(abp2_triplet_variants) == ARRAY_SIZE(abp2_range_config)); + +static int abp2_get_measurement(struct abp2_data *data) +{ + struct device *dev = data->dev; + int ret; + + reinit_completion(&data->completion); + + ret = data->ops->write(data, ABP2_CMD_SYNC, ABP2_PKT_SYNC_LEN); + if (ret < 0) + return ret; + + if (data->irq > 0) { + ret = wait_for_completion_timeout(&data->completion, HZ); + if (!ret) { + dev_err(dev, "timeout waiting for EOC interrupt\n"); + return -ETIMEDOUT; + } + } else { + fsleep(5 * USEC_PER_MSEC); + } + + memset(data->rx_buf, 0, sizeof(data->rx_buf)); + ret = data->ops->read(data, ABP2_CMD_NOP, ABP2_PKT_NOP_LEN); + if (ret < 0) + return ret; + + /* + * Status byte flags + * bit7 SANITY_CHK - must always be 0 + * bit6 ABP2_ST_POWER - 1 if device is powered + * bit5 ABP2_ST_BUSY - 1 if device has no new conversion ready + * bit4 SANITY_CHK - must always be 0 + * bit3 SANITY_CHK - must always be 0 + * bit2 MEMORY_ERR - 1 if integrity test has failed + * bit1 SANITY_CHK - must always be 0 + * bit0 MATH_ERR - 1 during internal math saturation error + */ + + if (data->rx_buf[0] == (ABP2_ST_POWER | ABP2_ST_BUSY)) + return -EBUSY; + + /* + * The ABP2 sensor series seem to have a noticeable latch-up sensitivity. + * A partial latch-up condition manifests as either + * - output of invalid status bytes + * - zeroed out conversions (despite a normal status byte) + * - the MOSI line being pulled low randomly in sync with the SCLK + * signal (visible during the ABP2_CMD_NOP command). + * https://e2e.ti.com/support/processors-group/processors/f/processors-forum/1588325/am3358-spi-tx-data-corruption + */ + + if (data->rx_buf[0] != ABP2_ST_POWER) { + dev_err(data->dev, + "unexpected status byte 0x%02x\n", data->rx_buf[0]); + return -EIO; + } + + return 0; +} + +static irqreturn_t abp2_eoc_handler(int irq, void *private) +{ + struct abp2_data *data = private; + + complete(&data->completion); + + return IRQ_HANDLED; +} + +static irqreturn_t abp2_trigger_handler(int irq, void *private) +{ + int ret; + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct abp2_data *data = iio_priv(indio_dev); + + ret = abp2_get_measurement(data); + if (ret < 0) + goto out_notify_done; + + data->scan.chan[0] = get_unaligned_be24(&data->rx_buf[1]); + data->scan.chan[1] = get_unaligned_be24(&data->rx_buf[4]); + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + iio_get_time_ns(indio_dev)); + +out_notify_done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +/* + * IIO ABI expects + * value = (conv + offset) * scale + * + * temp[C] = conv * a + b + * where a = 200/16777215; b = -50 + * + * temp[C] = (conv + (b/a)) * a * (1000) + * => + * scale = a * 1000 = .0000119209296 * 1000 = .01192092966562 + * offset = b/a = -50 * 16777215 / 200 = -4194303.75 + * + * pressure = (conv - Omin) * Q + Pmin = + * ((conv - Omin) + Pmin/Q) * Q + * => + * scale = Q = (Pmax - Pmin) / (Omax - Omin) + * offset = Pmin/Q - Omin = Pmin * (Omax - Omin) / (Pmax - Pmin) - Omin + */ +static int abp2_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct abp2_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = abp2_get_measurement(data); + if (ret < 0) + return ret; + + switch (channel->type) { + case IIO_PRESSURE: + *val = get_unaligned_be24(&data->rx_buf[1]); + return IIO_VAL_INT; + case IIO_TEMP: + *val = get_unaligned_be24(&data->rx_buf[4]); + return IIO_VAL_INT; + default: + return -EINVAL; + } + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (channel->type) { + case IIO_TEMP: + *val = 0; + *val2 = 11920929; + return IIO_VAL_INT_PLUS_NANO; + case IIO_PRESSURE: + *val = data->p_scale; + *val2 = data->p_scale_dec; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_OFFSET: + switch (channel->type) { + case IIO_TEMP: + *val = -4194304; + return IIO_VAL_INT; + case IIO_PRESSURE: + *val = data->p_offset; + return IIO_VAL_INT; + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static const struct iio_info abp2_info = { + .read_raw = &abp2_read_raw, +}; + +static const unsigned long abp2_scan_masks[] = {0x3, 0}; + +static const struct iio_chan_spec abp2_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +int abp2_common_probe(struct device *dev, const struct abp2_ops *ops, int irq) +{ + int ret; + struct abp2_data *data; + struct iio_dev *indio_dev; + const char *triplet; + s32 tmp; + s64 odelta, pdelta; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = dev; + data->ops = ops; + data->irq = irq; + + init_completion(&data->completion); + + indio_dev->name = "abp2030pa"; + indio_dev->info = &abp2_info; + indio_dev->channels = abp2_channels; + indio_dev->num_channels = ARRAY_SIZE(abp2_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->available_scan_masks = abp2_scan_masks; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "can't get and enable vdd supply\n"); + + ret = device_property_read_string(dev, "honeywell,pressure-triplet", + &triplet); + if (ret) { + ret = device_property_read_u32(dev, "honeywell,pmin-pascal", + &data->pmin); + if (ret) + return dev_err_probe(dev, ret, + "honeywell,pmin-pascal could not be read\n"); + + ret = device_property_read_u32(dev, "honeywell,pmax-pascal", + &data->pmax); + if (ret) + return dev_err_probe(dev, ret, + "honeywell,pmax-pascal could not be read\n"); + } else { + ret = device_property_match_property_string(dev, + "honeywell,pressure-triplet", + abp2_triplet_variants, + ARRAY_SIZE(abp2_triplet_variants)); + if (ret < 0) + return dev_err_probe(dev, -EINVAL, "honeywell,pressure-triplet is invalid\n"); + + data->pmin = abp2_range_config[ret].pmin; + data->pmax = abp2_range_config[ret].pmax; + } + + if (data->pmin >= data->pmax) + return dev_err_probe(dev, -EINVAL, "pressure limits are invalid\n"); + + data->outmin = abp2_func_spec[data->function].output_min; + data->outmax = abp2_func_spec[data->function].output_max; + + odelta = data->outmax - data->outmin; + pdelta = data->pmax - data->pmin; + + data->p_scale = div_s64_rem(div_s64(pdelta * NANO, odelta), NANO, &tmp); + data->p_scale_dec = tmp; + + data->p_offset = div_s64(odelta * data->pmin, pdelta) - data->outmin; + + if (data->irq > 0) { + ret = devm_request_irq(dev, irq, abp2_eoc_handler, IRQF_ONESHOT, + dev_name(dev), data); + if (ret) + return dev_err_probe(dev, ret, "request irq %d failed\n", data->irq); + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + abp2_trigger_handler, NULL); + if (ret) + return dev_err_probe(dev, ret, "iio triggered buffer setup failed\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "unable to register iio device\n"); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(abp2_common_probe, "IIO_HONEYWELL_ABP2030PA"); + +MODULE_AUTHOR("Petre Rodan "); +MODULE_DESCRIPTION("Honeywell ABP2 pressure sensor core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/abp2030pa.h b/drivers/iio/pressure/abp2030pa.h new file mode 100644 index 000000000000..57e5ed784686 --- /dev/null +++ b/drivers/iio/pressure/abp2030pa.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Honeywell ABP2 series pressure sensor driver + * + * Copyright (c) 2025 Petre Rodan + */ + +#ifndef _ABP2030PA_H +#define _ABP2030PA_H + +#include +#include + +#include + +#define ABP2_MEASUREMENT_RD_SIZE 7 + +struct device; + +struct abp2_data; +struct abp2_ops; + +enum abp2_func_id { + ABP2_FUNCTION_A, +}; + +/** + * struct abp2_data + * @dev: current device structure + * @ops: pointers for bus specific read and write functions + * @pmin: minimal pressure in pascal + * @pmax: maximal pressure in pascal + * @outmin: minimum raw pressure in counts (based on transfer function) + * @outmax: maximum raw pressure in counts (based on transfer function) + * @function: transfer function + * @p_scale: pressure scale + * @p_scale_dec: pressure scale, decimal number + * @p_offset: pressure offset + * @irq: end of conversion - applies only to the i2c sensor + * @completion: handshake from irq to read + * @scan: channel values for buffered mode + * @tx_buf: transmit buffer used during the SPI communication + * @rx_buf: raw data provided by sensor + */ +struct abp2_data { + struct device *dev; + const struct abp2_ops *ops; + s32 pmin; + s32 pmax; + u32 outmin; + u32 outmax; + enum abp2_func_id function; + int p_scale; + int p_scale_dec; + int p_offset; + int irq; + struct completion completion; + struct { + u32 chan[2]; + aligned_s64 timestamp; + } scan; + u8 rx_buf[ABP2_MEASUREMENT_RD_SIZE] __aligned(IIO_DMA_MINALIGN); + u8 tx_buf[ABP2_MEASUREMENT_RD_SIZE]; +}; + +struct abp2_ops { + int (*read)(struct abp2_data *data, u8 cmd, u8 nbytes); + int (*write)(struct abp2_data *data, u8 cmd, u8 nbytes); +}; + +int abp2_common_probe(struct device *dev, const struct abp2_ops *ops, int irq); + +#endif diff --git a/drivers/iio/pressure/abp2030pa_i2c.c b/drivers/iio/pressure/abp2030pa_i2c.c new file mode 100644 index 000000000000..9f1c1c8a9afb --- /dev/null +++ b/drivers/iio/pressure/abp2030pa_i2c.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Honeywell ABP2 series pressure sensor driver + * + * Copyright (c) 2025 Petre Rodan + */ + +#include +#include +#include +#include +#include +#include + +#include "abp2030pa.h" + +static int abp2_i2c_read(struct abp2_data *data, u8 unused, u8 nbytes) +{ + struct i2c_client *client = to_i2c_client(data->dev); + int ret; + + if (nbytes > ABP2_MEASUREMENT_RD_SIZE) + return -EOVERFLOW; + + ret = i2c_master_recv(client, data->rx_buf, nbytes); + if (ret < 0) + return ret; + if (ret != nbytes) + return -EIO; + + return 0; +} + +static int abp2_i2c_write(struct abp2_data *data, u8 cmd, u8 nbytes) +{ + struct i2c_client *client = to_i2c_client(data->dev); + int ret; + + if (nbytes > ABP2_MEASUREMENT_RD_SIZE) + return -EOVERFLOW; + + data->tx_buf[0] = cmd; + ret = i2c_master_send(client, data->tx_buf, nbytes); + if (ret < 0) + return ret; + if (ret != nbytes) + return -EIO; + + return 0; +} + +static const struct abp2_ops abp2_i2c_ops = { + .read = abp2_i2c_read, + .write = abp2_i2c_write, +}; + +static int abp2_i2c_probe(struct i2c_client *client) +{ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + return abp2_common_probe(&client->dev, &abp2_i2c_ops, client->irq); +} + +static const struct of_device_id abp2_i2c_match[] = { + { .compatible = "honeywell,abp2030pa" }, + { } +}; +MODULE_DEVICE_TABLE(of, abp2_i2c_match); + +static const struct i2c_device_id abp2_i2c_id[] = { + { "abp2030pa" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, abp2_i2c_id); + +static struct i2c_driver abp2_i2c_driver = { + .driver = { + .name = "abp2030pa", + .of_match_table = abp2_i2c_match, + }, + .probe = abp2_i2c_probe, + .id_table = abp2_i2c_id, +}; +module_i2c_driver(abp2_i2c_driver); + +MODULE_AUTHOR("Petre Rodan "); +MODULE_DESCRIPTION("Honeywell ABP2 pressure sensor i2c driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HONEYWELL_ABP2030PA"); diff --git a/drivers/iio/pressure/abp2030pa_spi.c b/drivers/iio/pressure/abp2030pa_spi.c new file mode 100644 index 000000000000..eaea9a3ebf11 --- /dev/null +++ b/drivers/iio/pressure/abp2030pa_spi.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Honeywell ABP2 series pressure sensor driver + * + * Copyright (c) 2025 Petre Rodan + */ + +#include +#include +#include +#include +#include + +#include "abp2030pa.h" + +static int abp2_spi_xfer(struct abp2_data *data, u8 cmd, u8 nbytes) +{ + struct spi_device *spi = to_spi_device(data->dev); + struct spi_transfer xfer = { }; + + if (nbytes > ABP2_MEASUREMENT_RD_SIZE) + return -EOVERFLOW; + + data->tx_buf[0] = cmd; + xfer.tx_buf = data->tx_buf; + xfer.rx_buf = data->rx_buf; + xfer.len = nbytes; + + return spi_sync_transfer(spi, &xfer, 1); +} + +static const struct abp2_ops abp2_spi_ops = { + .read = abp2_spi_xfer, + .write = abp2_spi_xfer, +}; + +static int abp2_spi_probe(struct spi_device *spi) +{ + return abp2_common_probe(&spi->dev, &abp2_spi_ops, spi->irq); +} + +static const struct of_device_id abp2_spi_match[] = { + { .compatible = "honeywell,abp2030pa" }, + { } +}; +MODULE_DEVICE_TABLE(of, abp2_spi_match); + +static const struct spi_device_id abp2_spi_id[] = { + { "abp2030pa" }, + { } +}; +MODULE_DEVICE_TABLE(spi, abp2_spi_id); + +static struct spi_driver abp2_spi_driver = { + .driver = { + .name = "abp2030pa", + .of_match_table = abp2_spi_match, + }, + .probe = abp2_spi_probe, + .id_table = abp2_spi_id, +}; +module_spi_driver(abp2_spi_driver); + +MODULE_AUTHOR("Petre Rodan "); +MODULE_DESCRIPTION("Honeywell ABP2 pressure sensor spi driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HONEYWELL_ABP2030PA"); From 634a6316617e2ced7016b002d0b284a1502e9601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Fri, 19 Dec 2025 15:40:38 +0000 Subject: [PATCH 034/297] iio: adc: adi-axi-adc: Make use of dev_err_probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Be consistent and use dev_err_probe() as in all other places in the .probe() path. While at it, remove the line break in the version condition. Yes, it goes over the 80 column limit but I do think the line break hurts readability in this case. And use a struct device *dev helper for neater code. Signed-off-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/adc/adi-axi-adc.c | 55 ++++++++++++++++------------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c index 14fa4238c2b9..fef4fdedb976 100644 --- a/drivers/iio/adc/adi-axi-adc.c +++ b/drivers/iio/adc/adi-axi-adc.c @@ -674,13 +674,14 @@ static const struct iio_backend_info axi_ad408x = { static int adi_axi_adc_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; struct adi_axi_adc_state *st; void __iomem *base; unsigned int ver; struct clk *clk; int ret; - st = devm_kzalloc(&pdev->dev, sizeof(*st), GFP_KERNEL); + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); if (!st) return -ENOMEM; @@ -688,20 +689,19 @@ static int adi_axi_adc_probe(struct platform_device *pdev) if (IS_ERR(base)) return PTR_ERR(base); - st->dev = &pdev->dev; - st->regmap = devm_regmap_init_mmio(&pdev->dev, base, - &axi_adc_regmap_config); + st->dev = dev; + st->regmap = devm_regmap_init_mmio(dev, base, &axi_adc_regmap_config); if (IS_ERR(st->regmap)) - return dev_err_probe(&pdev->dev, PTR_ERR(st->regmap), + return dev_err_probe(dev, PTR_ERR(st->regmap), "failed to init register map\n"); - st->info = device_get_match_data(&pdev->dev); + st->info = device_get_match_data(dev); if (!st->info) return -ENODEV; - clk = devm_clk_get_enabled(&pdev->dev, NULL); + clk = devm_clk_get_enabled(dev, NULL); if (IS_ERR(clk)) - return dev_err_probe(&pdev->dev, PTR_ERR(clk), + return dev_err_probe(dev, PTR_ERR(clk), "failed to get clock\n"); /* @@ -716,47 +716,42 @@ static int adi_axi_adc_probe(struct platform_device *pdev) if (ret) return ret; - if (ADI_AXI_PCORE_VER_MAJOR(ver) != - ADI_AXI_PCORE_VER_MAJOR(st->info->version)) { - dev_err(&pdev->dev, - "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n", - ADI_AXI_PCORE_VER_MAJOR(st->info->version), - ADI_AXI_PCORE_VER_MINOR(st->info->version), - ADI_AXI_PCORE_VER_PATCH(st->info->version), - ADI_AXI_PCORE_VER_MAJOR(ver), - ADI_AXI_PCORE_VER_MINOR(ver), - ADI_AXI_PCORE_VER_PATCH(ver)); - return -ENODEV; - } + if (ADI_AXI_PCORE_VER_MAJOR(ver) != ADI_AXI_PCORE_VER_MAJOR(st->info->version)) + return dev_err_probe(dev, -ENODEV, + "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n", + ADI_AXI_PCORE_VER_MAJOR(st->info->version), + ADI_AXI_PCORE_VER_MINOR(st->info->version), + ADI_AXI_PCORE_VER_PATCH(st->info->version), + ADI_AXI_PCORE_VER_MAJOR(ver), + ADI_AXI_PCORE_VER_MINOR(ver), + ADI_AXI_PCORE_VER_PATCH(ver)); - ret = devm_iio_backend_register(&pdev->dev, st->info->backend_info, st); + ret = devm_iio_backend_register(dev, st->info->backend_info, st); if (ret) - return dev_err_probe(&pdev->dev, ret, - "failed to register iio backend\n"); + return dev_err_probe(dev, ret, "failed to register iio backend\n"); - device_for_each_child_node_scoped(&pdev->dev, child) { + device_for_each_child_node_scoped(dev, child) { int val; if (!st->info->has_child_nodes) - return dev_err_probe(&pdev->dev, -EINVAL, + return dev_err_probe(dev, -EINVAL, "invalid fdt axi-dac compatible."); /* Processing only reg 0 node */ ret = fwnode_property_read_u32(child, "reg", &val); if (ret) - return dev_err_probe(&pdev->dev, ret, - "invalid reg property."); + return dev_err_probe(dev, ret, "invalid reg property."); if (val != 0) - return dev_err_probe(&pdev->dev, -EINVAL, + return dev_err_probe(dev, -EINVAL, "invalid node address."); ret = axi_adc_create_platform_device(st, child); if (ret) - return dev_err_probe(&pdev->dev, -EINVAL, + return dev_err_probe(dev, -EINVAL, "cannot create device."); } - dev_info(&pdev->dev, "AXI ADC IP core (%d.%.2d.%c) probed\n", + dev_info(dev, "AXI ADC IP core (%d.%.2d.%c) probed\n", ADI_AXI_PCORE_VER_MAJOR(ver), ADI_AXI_PCORE_VER_MINOR(ver), ADI_AXI_PCORE_VER_PATCH(ver)); From 0a272aaf5fc110a34f4a5aeb61db4b6d105c62e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Fri, 19 Dec 2025 15:40:39 +0000 Subject: [PATCH 035/297] iio: adc: adi-axi-adc: Slightly simplify axi_adc_create_platform_device() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There's no point in having a ret variable and checking for errors (as we do nothing with it). Instead, save some lines of code and directly return the devm_add_action_or_reset() call. Reviewed-by: Andy Shevchenko Signed-off-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/adc/adi-axi-adc.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c index fef4fdedb976..5f445e0de9ea 100644 --- a/drivers/iio/adc/adi-axi-adc.c +++ b/drivers/iio/adc/adi-axi-adc.c @@ -591,17 +591,12 @@ static int axi_adc_create_platform_device(struct adi_axi_adc_state *st, .size_data = st->info->pdata_sz, }; struct platform_device *pdev; - int ret; pdev = platform_device_register_full(&pi); if (IS_ERR(pdev)) return PTR_ERR(pdev); - ret = devm_add_action_or_reset(st->dev, axi_adc_child_remove, pdev); - if (ret) - return ret; - - return 0; + return devm_add_action_or_reset(st->dev, axi_adc_child_remove, pdev); } static const struct iio_backend_ops adi_axi_adc_ops = { From 09ccc1b65ca6bb61d05f163e063b98d89d4d7d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Fri, 19 Dec 2025 15:28:12 +0000 Subject: [PATCH 036/297] iio: buffer-dma: Use lockdep for locking annotations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Don't use mutex_is_locked() + WARN_ON() for checking if a specif lock is taken. Instead use the existing annotations which means lockdep_assert_held(). Signed-off-by: Nuno Sá Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/buffer/industrialio-buffer-dma.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/iio/buffer/industrialio-buffer-dma.c b/drivers/iio/buffer/industrialio-buffer-dma.c index 7a7a9d37339b..8906feaeb69b 100644 --- a/drivers/iio/buffer/industrialio-buffer-dma.c +++ b/drivers/iio/buffer/industrialio-buffer-dma.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -764,7 +765,7 @@ int iio_dma_buffer_enqueue_dmabuf(struct iio_buffer *buffer, bool cookie; int ret; - WARN_ON(!mutex_is_locked(&queue->lock)); + lockdep_assert_held(&queue->lock); cookie = dma_fence_begin_signalling(); From 07d6dc1708838e94e3da7294e601adf8da1d9414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Fri, 19 Dec 2025 15:28:13 +0000 Subject: [PATCH 037/297] iio: buffer-dma: Use the cleanup.h API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make use of the cleanup.h API for locks and memory allocation in order to simplify some code paths. Signed-off-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/buffer/industrialio-buffer-dma.c | 155 ++++++++----------- 1 file changed, 62 insertions(+), 93 deletions(-) diff --git a/drivers/iio/buffer/industrialio-buffer-dma.c b/drivers/iio/buffer/industrialio-buffer-dma.c index 8906feaeb69b..49d1bff5242a 100644 --- a/drivers/iio/buffer/industrialio-buffer-dma.c +++ b/drivers/iio/buffer/industrialio-buffer-dma.c @@ -136,9 +136,8 @@ static void iio_dma_buffer_cleanup_worker(struct work_struct *work) struct iio_dma_buffer_block *block, *_block; LIST_HEAD(block_list); - spin_lock_irq(&iio_dma_buffer_dead_blocks_lock); - list_splice_tail_init(&iio_dma_buffer_dead_blocks, &block_list); - spin_unlock_irq(&iio_dma_buffer_dead_blocks_lock); + scoped_guard(spinlock_irq, &iio_dma_buffer_dead_blocks_lock) + list_splice_tail_init(&iio_dma_buffer_dead_blocks, &block_list); list_for_each_entry_safe(block, _block, &block_list, head) iio_buffer_block_release(&block->kref); @@ -148,13 +147,11 @@ static DECLARE_WORK(iio_dma_buffer_cleanup_work, iio_dma_buffer_cleanup_worker); static void iio_buffer_block_release_atomic(struct kref *kref) { struct iio_dma_buffer_block *block; - unsigned long flags; block = container_of(kref, struct iio_dma_buffer_block, kref); - spin_lock_irqsave(&iio_dma_buffer_dead_blocks_lock, flags); - list_add_tail(&block->head, &iio_dma_buffer_dead_blocks); - spin_unlock_irqrestore(&iio_dma_buffer_dead_blocks_lock, flags); + scoped_guard(spinlock_irqsave, &iio_dma_buffer_dead_blocks_lock) + list_add_tail(&block->head, &iio_dma_buffer_dead_blocks); schedule_work(&iio_dma_buffer_cleanup_work); } @@ -175,19 +172,16 @@ static struct iio_dma_buffer_queue *iio_buffer_to_queue(struct iio_buffer *buf) static struct iio_dma_buffer_block *iio_dma_buffer_alloc_block( struct iio_dma_buffer_queue *queue, size_t size, bool fileio) { - struct iio_dma_buffer_block *block; - - block = kzalloc(sizeof(*block), GFP_KERNEL); + struct iio_dma_buffer_block *block __free(kfree) = + kzalloc(sizeof(*block), GFP_KERNEL); if (!block) return NULL; if (fileio) { block->vaddr = dma_alloc_coherent(queue->dev, PAGE_ALIGN(size), &block->phys_addr, GFP_KERNEL); - if (!block->vaddr) { - kfree(block); + if (!block->vaddr) return NULL; - } } block->fileio = fileio; @@ -202,7 +196,7 @@ static struct iio_dma_buffer_block *iio_dma_buffer_alloc_block( if (!fileio) atomic_inc(&queue->num_dmabufs); - return block; + return_ptr(block); } static void _iio_dma_buffer_block_done(struct iio_dma_buffer_block *block) @@ -233,14 +227,12 @@ static void iio_dma_buffer_queue_wake(struct iio_dma_buffer_queue *queue) void iio_dma_buffer_block_done(struct iio_dma_buffer_block *block) { struct iio_dma_buffer_queue *queue = block->queue; - unsigned long flags; bool cookie; cookie = dma_fence_begin_signalling(); - spin_lock_irqsave(&queue->list_lock, flags); - _iio_dma_buffer_block_done(block); - spin_unlock_irqrestore(&queue->list_lock, flags); + scoped_guard(spinlock_irqsave, &queue->list_lock) + _iio_dma_buffer_block_done(block); if (!block->fileio) iio_buffer_signal_dmabuf_done(block->fence, 0); @@ -265,22 +257,22 @@ void iio_dma_buffer_block_list_abort(struct iio_dma_buffer_queue *queue, struct list_head *list) { struct iio_dma_buffer_block *block, *_block; - unsigned long flags; bool cookie; cookie = dma_fence_begin_signalling(); - spin_lock_irqsave(&queue->list_lock, flags); - list_for_each_entry_safe(block, _block, list, head) { - list_del(&block->head); - block->bytes_used = 0; - _iio_dma_buffer_block_done(block); + scoped_guard(spinlock_irqsave, &queue->list_lock) { + list_for_each_entry_safe(block, _block, list, head) { + list_del(&block->head); + block->bytes_used = 0; + _iio_dma_buffer_block_done(block); - if (!block->fileio) - iio_buffer_signal_dmabuf_done(block->fence, -EINTR); - iio_buffer_block_put_atomic(block); + if (!block->fileio) + iio_buffer_signal_dmabuf_done(block->fence, + -EINTR); + iio_buffer_block_put_atomic(block); + } } - spin_unlock_irqrestore(&queue->list_lock, flags); if (queue->fileio.enabled) queue->fileio.enabled = false; @@ -329,7 +321,6 @@ int iio_dma_buffer_request_update(struct iio_buffer *buffer) struct iio_dma_buffer_block *block; bool try_reuse = false; size_t size; - int ret = 0; int i; /* @@ -340,13 +331,13 @@ int iio_dma_buffer_request_update(struct iio_buffer *buffer) size = DIV_ROUND_UP(queue->buffer.bytes_per_datum * queue->buffer.length, 2); - mutex_lock(&queue->lock); + guard(mutex)(&queue->lock); queue->fileio.enabled = iio_dma_buffer_can_use_fileio(queue); /* If DMABUFs were created, disable fileio interface */ if (!queue->fileio.enabled) - goto out_unlock; + return 0; /* Allocations are page aligned */ if (PAGE_ALIGN(queue->fileio.block_size) == PAGE_ALIGN(size)) @@ -355,22 +346,22 @@ int iio_dma_buffer_request_update(struct iio_buffer *buffer) queue->fileio.block_size = size; queue->fileio.active_block = NULL; - spin_lock_irq(&queue->list_lock); - for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { - block = queue->fileio.blocks[i]; + scoped_guard(spinlock_irq, &queue->list_lock) { + for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { + block = queue->fileio.blocks[i]; - /* If we can't re-use it free it */ - if (block && (!iio_dma_block_reusable(block) || !try_reuse)) - block->state = IIO_BLOCK_STATE_DEAD; + /* If we can't re-use it free it */ + if (block && (!iio_dma_block_reusable(block) || !try_reuse)) + block->state = IIO_BLOCK_STATE_DEAD; + } + + /* + * At this point all blocks are either owned by the core or + * marked as dead. This means we can reset the lists without + * having to fear corruption. + */ } - /* - * At this point all blocks are either owned by the core or marked as - * dead. This means we can reset the lists without having to fear - * corrution. - */ - spin_unlock_irq(&queue->list_lock); - INIT_LIST_HEAD(&queue->incoming); for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { @@ -389,10 +380,9 @@ int iio_dma_buffer_request_update(struct iio_buffer *buffer) if (!block) { block = iio_dma_buffer_alloc_block(queue, size, true); - if (!block) { - ret = -ENOMEM; - goto out_unlock; - } + if (!block) + return -ENOMEM; + queue->fileio.blocks[i] = block; } @@ -416,10 +406,7 @@ int iio_dma_buffer_request_update(struct iio_buffer *buffer) } } -out_unlock: - mutex_unlock(&queue->lock); - - return ret; + return 0; } EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_request_update, "IIO_DMA_BUFFER"); @@ -427,13 +414,13 @@ static void iio_dma_buffer_fileio_free(struct iio_dma_buffer_queue *queue) { unsigned int i; - spin_lock_irq(&queue->list_lock); - for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { - if (!queue->fileio.blocks[i]) - continue; - queue->fileio.blocks[i]->state = IIO_BLOCK_STATE_DEAD; + scoped_guard(spinlock_irq, &queue->list_lock) { + for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { + if (!queue->fileio.blocks[i]) + continue; + queue->fileio.blocks[i]->state = IIO_BLOCK_STATE_DEAD; + } } - spin_unlock_irq(&queue->list_lock); INIT_LIST_HEAD(&queue->incoming); @@ -497,13 +484,12 @@ int iio_dma_buffer_enable(struct iio_buffer *buffer, struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); struct iio_dma_buffer_block *block, *_block; - mutex_lock(&queue->lock); + guard(mutex)(&queue->lock); queue->active = true; list_for_each_entry_safe(block, _block, &queue->incoming, head) { list_del(&block->head); iio_dma_buffer_submit_block(queue, block); } - mutex_unlock(&queue->lock); return 0; } @@ -522,12 +508,11 @@ int iio_dma_buffer_disable(struct iio_buffer *buffer, { struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); - mutex_lock(&queue->lock); + guard(mutex)(&queue->lock); queue->active = false; if (queue->ops && queue->ops->abort) queue->ops->abort(queue); - mutex_unlock(&queue->lock); return 0; } @@ -552,19 +537,16 @@ static struct iio_dma_buffer_block *iio_dma_buffer_dequeue( struct iio_dma_buffer_block *block; unsigned int idx; - spin_lock_irq(&queue->list_lock); + guard(spinlock_irq)(&queue->list_lock); idx = queue->fileio.next_dequeue; block = queue->fileio.blocks[idx]; - if (block->state == IIO_BLOCK_STATE_DONE) { - idx = (idx + 1) % ARRAY_SIZE(queue->fileio.blocks); - queue->fileio.next_dequeue = idx; - } else { - block = NULL; - } + if (block->state != IIO_BLOCK_STATE_DONE) + return NULL; - spin_unlock_irq(&queue->list_lock); + idx = (idx + 1) % ARRAY_SIZE(queue->fileio.blocks); + queue->fileio.next_dequeue = idx; return block; } @@ -580,14 +562,13 @@ static int iio_dma_buffer_io(struct iio_buffer *buffer, size_t n, if (n < buffer->bytes_per_datum) return -EINVAL; - mutex_lock(&queue->lock); + guard(mutex)(&queue->lock); if (!queue->fileio.active_block) { block = iio_dma_buffer_dequeue(queue); - if (block == NULL) { - ret = 0; - goto out_unlock; - } + if (!block) + return 0; + queue->fileio.pos = 0; queue->fileio.active_block = block; } else { @@ -603,10 +584,8 @@ static int iio_dma_buffer_io(struct iio_buffer *buffer, size_t n, ret = copy_from_user(addr, user_buffer, n); else ret = copy_to_user(user_buffer, addr, n); - if (ret) { - ret = -EFAULT; - goto out_unlock; - } + if (ret) + return -EFAULT; queue->fileio.pos += n; @@ -615,12 +594,7 @@ static int iio_dma_buffer_io(struct iio_buffer *buffer, size_t n, iio_dma_buffer_enqueue(queue, block); } - ret = n; - -out_unlock: - mutex_unlock(&queue->lock); - - return ret; + return n; } /** @@ -678,11 +652,11 @@ size_t iio_dma_buffer_usage(struct iio_buffer *buf) * but won't increase since all blocks are in use. */ - mutex_lock(&queue->lock); + guard(mutex)(&queue->lock); if (queue->fileio.active_block) data_available += queue->fileio.active_block->size; - spin_lock_irq(&queue->list_lock); + guard(spinlock_irq)(&queue->list_lock); for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { block = queue->fileio.blocks[i]; @@ -692,9 +666,6 @@ size_t iio_dma_buffer_usage(struct iio_buffer *buf) data_available += block->size; } - spin_unlock_irq(&queue->list_lock); - mutex_unlock(&queue->lock); - return data_available; } EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_usage, "IIO_DMA_BUFFER"); @@ -882,12 +853,10 @@ EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_init, "IIO_DMA_BUFFER"); */ void iio_dma_buffer_exit(struct iio_dma_buffer_queue *queue) { - mutex_lock(&queue->lock); + guard(mutex)(&queue->lock); iio_dma_buffer_fileio_free(queue); queue->ops = NULL; - - mutex_unlock(&queue->lock); } EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_exit, "IIO_DMA_BUFFER"); From 247a357a9101b76d832b7e19d85ba8dcfb3e3e8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Fri, 19 Dec 2025 15:28:14 +0000 Subject: [PATCH 038/297] iio: buffer-dma: Turn iio_dma_buffer_init() void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iio_dma_buffer_init() always return 0. Therefore there's no point in returning int. While at it, fix a mismatch between the function declaration and definition regarding the struct device (dma_dev != dev). Signed-off-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/buffer/industrialio-buffer-dma.c | 6 ++---- include/linux/iio/buffer-dma.h | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/iio/buffer/industrialio-buffer-dma.c b/drivers/iio/buffer/industrialio-buffer-dma.c index 49d1bff5242a..ed187bb23708 100644 --- a/drivers/iio/buffer/industrialio-buffer-dma.c +++ b/drivers/iio/buffer/industrialio-buffer-dma.c @@ -826,8 +826,8 @@ EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_set_length, "IIO_DMA_BUFFER"); * should refer to the device that will perform the DMA to ensure that * allocations are done from a memory region that can be accessed by the device. */ -int iio_dma_buffer_init(struct iio_dma_buffer_queue *queue, - struct device *dev, const struct iio_dma_buffer_ops *ops) +void iio_dma_buffer_init(struct iio_dma_buffer_queue *queue, struct device *dev, + const struct iio_dma_buffer_ops *ops) { iio_buffer_init(&queue->buffer); queue->buffer.length = PAGE_SIZE; @@ -839,8 +839,6 @@ int iio_dma_buffer_init(struct iio_dma_buffer_queue *queue, mutex_init(&queue->lock); spin_lock_init(&queue->list_lock); - - return 0; } EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_init, "IIO_DMA_BUFFER"); diff --git a/include/linux/iio/buffer-dma.h b/include/linux/iio/buffer-dma.h index 4f33e6a39797..cd2ba4bb7501 100644 --- a/include/linux/iio/buffer-dma.h +++ b/include/linux/iio/buffer-dma.h @@ -157,8 +157,8 @@ int iio_dma_buffer_set_bytes_per_datum(struct iio_buffer *buffer, size_t bpd); int iio_dma_buffer_set_length(struct iio_buffer *buffer, unsigned int length); int iio_dma_buffer_request_update(struct iio_buffer *buffer); -int iio_dma_buffer_init(struct iio_dma_buffer_queue *queue, - struct device *dma_dev, const struct iio_dma_buffer_ops *ops); +void iio_dma_buffer_init(struct iio_dma_buffer_queue *queue, struct device *dev, + const struct iio_dma_buffer_ops *ops); void iio_dma_buffer_exit(struct iio_dma_buffer_queue *queue); void iio_dma_buffer_release(struct iio_dma_buffer_queue *queue); From dee49a1860168f912e3b3444626c060e9e72d491 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Mon, 24 Nov 2025 12:06:52 +0100 Subject: [PATCH 039/297] dt-bindings: interconnect: mt8183-emi: Add support for MT8196 EMI Add a new compatible for the External Memory Interface Interconnect found on the MediaTek MT8196 Chromebook SoC. Signed-off-by: AngeloGioacchino Del Regno Acked-by: Rob Herring (Arm) Signed-off-by: Nicolas Frattaroli Link: https://lore.kernel.org/r/20251124-mt8196-dvfsrc-v2-3-d9c1334db9f3@collabora.com Signed-off-by: Georgi Djakov --- .../interconnect/mediatek,mt8183-emi.yaml | 1 + .../interconnect/mediatek,mt8196.h | 48 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 include/dt-bindings/interconnect/mediatek,mt8196.h diff --git a/Documentation/devicetree/bindings/interconnect/mediatek,mt8183-emi.yaml b/Documentation/devicetree/bindings/interconnect/mediatek,mt8183-emi.yaml index 017c8478b2a7..1fb8ccb558fb 100644 --- a/Documentation/devicetree/bindings/interconnect/mediatek,mt8183-emi.yaml +++ b/Documentation/devicetree/bindings/interconnect/mediatek,mt8183-emi.yaml @@ -40,6 +40,7 @@ properties: enum: - mediatek,mt8183-emi - mediatek,mt8195-emi + - mediatek,mt8196-emi '#interconnect-cells': const: 1 diff --git a/include/dt-bindings/interconnect/mediatek,mt8196.h b/include/dt-bindings/interconnect/mediatek,mt8196.h new file mode 100644 index 000000000000..de700fa73223 --- /dev/null +++ b/include/dt-bindings/interconnect/mediatek,mt8196.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (c) 2025 Collabora Ltd. + * AngeloGioacchino Del Regno + */ + +#ifndef __DT_BINDINGS_INTERCONNECT_MEDIATEK_MT8196_H +#define __DT_BINDINGS_INTERCONNECT_MEDIATEK_MT8196_H + +#define SLAVE_DDR_EMI 0 +#define MASTER_MCUSYS 1 +#define MASTER_MCU_0 2 +#define MASTER_MCU_1 3 +#define MASTER_MCU_2 4 +#define MASTER_MCU_3 5 +#define MASTER_MCU_4 6 +#define MASTER_GPUSYS 7 +#define MASTER_MMSYS 8 +#define MASTER_MM_VPU 9 +#define MASTER_MM_DISP 10 +#define MASTER_MM_VDEC 11 +#define MASTER_MM_VENC 12 +#define MASTER_MM_CAM 13 +#define MASTER_MM_IMG 14 +#define MASTER_MM_MDP 15 +#define MASTER_VPUSYS 16 +#define MASTER_VPU_0 17 +#define MASTER_VPU_1 18 +#define MASTER_MDLASYS 19 +#define MASTER_MDLA_0 20 +#define MASTER_UFS 21 +#define MASTER_PCIE 22 +#define MASTER_USB 23 +#define MASTER_WIFI 24 +#define MASTER_BT 25 +#define MASTER_NETSYS 26 +#define MASTER_DBGIF 27 +#define SLAVE_HRT_DDR_EMI 28 +#define MASTER_HRT_MMSYS 29 +#define MASTER_HRT_MM_DISP 30 +#define MASTER_HRT_MM_VDEC 31 +#define MASTER_HRT_MM_VENC 32 +#define MASTER_HRT_MM_CAM 33 +#define MASTER_HRT_MM_IMG 34 +#define MASTER_HRT_MM_MDP 35 +#define MASTER_HRT_ADSP 36 +#define MASTER_HRT_DBGIF 37 +#endif From ce69a970390ce182ccece5dea61083588ca5379a Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Mon, 24 Nov 2025 12:07:00 +0100 Subject: [PATCH 040/297] interconnect: mediatek: Add support for MediaTek MT8196 EMI ICC Add a new driver with data to register the External Memory Interface (EMI) Interconnect on the MediaTek MT8196 Chromebook SoC. Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Nicolas Frattaroli Link: https://lore.kernel.org/r/20251124-mt8196-dvfsrc-v2-11-d9c1334db9f3@collabora.com Signed-off-by: Georgi Djakov --- drivers/interconnect/mediatek/Kconfig | 7 + drivers/interconnect/mediatek/Makefile | 1 + drivers/interconnect/mediatek/mt8196.c | 383 +++++++++++++++++++++++++ 3 files changed, 391 insertions(+) create mode 100644 drivers/interconnect/mediatek/mt8196.c diff --git a/drivers/interconnect/mediatek/Kconfig b/drivers/interconnect/mediatek/Kconfig index 985c849efac3..9fd3f2170443 100644 --- a/drivers/interconnect/mediatek/Kconfig +++ b/drivers/interconnect/mediatek/Kconfig @@ -27,3 +27,10 @@ config INTERCONNECT_MTK_MT8195 help This is a driver for the MediaTek bus interconnect on MT8195-based platforms. + +config INTERCONNECT_MTK_MT8196 + tristate "MediaTek MT8196 interconnect driver" + depends on INTERCONNECT_MTK_DVFSRC_EMI + help + This is a driver for the MediaTek bus interconnect on MT8196-based + platforms. diff --git a/drivers/interconnect/mediatek/Makefile b/drivers/interconnect/mediatek/Makefile index 8e2283a9a5b5..6bd656668f5d 100644 --- a/drivers/interconnect/mediatek/Makefile +++ b/drivers/interconnect/mediatek/Makefile @@ -3,3 +3,4 @@ obj-$(CONFIG_INTERCONNECT_MTK_DVFSRC_EMI) += icc-emi.o obj-$(CONFIG_INTERCONNECT_MTK_MT8183) += mt8183.o obj-$(CONFIG_INTERCONNECT_MTK_MT8195) += mt8195.o +obj-$(CONFIG_INTERCONNECT_MTK_MT8195) += mt8196.o diff --git a/drivers/interconnect/mediatek/mt8196.c b/drivers/interconnect/mediatek/mt8196.c new file mode 100644 index 000000000000..e9af32065be1 --- /dev/null +++ b/drivers/interconnect/mediatek/mt8196.c @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2025 Collabora Ltd. + * AngeloGioacchino Del Regno + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "icc-emi.h" + +static struct mtk_icc_node ddr_emi = { + .name = "ddr-emi", + .id = SLAVE_DDR_EMI, + .ep = 1, +}; + +static struct mtk_icc_node mcusys = { + .name = "mcusys", + .id = MASTER_MCUSYS, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node mcu_port0 = { + .name = "mcu-port0", + .id = MASTER_MCU_0, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node mcu_port1 = { + .name = "mcu-port1", + .id = MASTER_MCU_1, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node mcu_port2 = { + .name = "mcu-port2", + .id = MASTER_MCU_2, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node mcu_port3 = { + .name = "mcu-port3", + .id = MASTER_MCU_3, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node mcu_port4 = { + .name = "mcu-port4", + .id = MASTER_MCU_4, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node gpu = { + .name = "gpu", + .id = MASTER_GPUSYS, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node mmsys = { + .name = "mmsys", + .id = MASTER_MMSYS, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node mm_vpu = { + .name = "mm-vpu", + .id = MASTER_MM_VPU, + .ep = 0, + .num_links = 1, + .links = { MASTER_MMSYS } +}; + +static struct mtk_icc_node mm_disp = { + .name = "mm-disp", + .id = MASTER_MM_DISP, + .ep = 0, + .num_links = 1, + .links = { MASTER_MMSYS } +}; + +static struct mtk_icc_node mm_vdec = { + .name = "mm-vdec", + .id = MASTER_MM_VDEC, + .ep = 0, + .num_links = 1, + .links = { MASTER_MMSYS } +}; + +static struct mtk_icc_node mm_venc = { + .name = "mm-venc", + .id = MASTER_MM_VENC, + .ep = 0, + .num_links = 1, + .links = { MASTER_MMSYS } +}; + +static struct mtk_icc_node mm_cam = { + .name = "mm-cam", + .id = MASTER_MM_CAM, + .ep = 0, + .num_links = 1, + .links = { MASTER_MMSYS } +}; + +static struct mtk_icc_node mm_img = { + .name = "mm-img", + .id = MASTER_MM_IMG, + .ep = 0, + .num_links = 1, + .links = { MASTER_MMSYS } +}; + +static struct mtk_icc_node mm_mdp = { + .name = "mm-mdp", + .id = MASTER_MM_MDP, + .ep = 0, + .num_links = 1, + .links = { MASTER_MMSYS } +}; + +static struct mtk_icc_node vpusys = { + .name = "vpusys", + .id = MASTER_VPUSYS, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node vpu_port0 = { + .name = "vpu-port0", + .id = MASTER_VPU_0, + .ep = 0, + .num_links = 1, + .links = { MASTER_VPUSYS } +}; + +static struct mtk_icc_node vpu_port1 = { + .name = "vpu-port1", + .id = MASTER_VPU_1, + .ep = 0, + .num_links = 1, + .links = { MASTER_VPUSYS } +}; + +static struct mtk_icc_node mdlasys = { + .name = "mdlasys", + .id = MASTER_MDLASYS, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node mdla_port0 = { + .name = "mdla-port0", + .id = MASTER_MDLA_0, + .ep = 0, + .num_links = 1, + .links = { MASTER_MDLASYS } +}; + +static struct mtk_icc_node ufs = { + .name = "ufs", + .id = MASTER_UFS, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node pcie = { + .name = "pcie", + .id = MASTER_PCIE, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node usb = { + .name = "usb", + .id = MASTER_USB, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node wifi = { + .name = "wifi", + .id = MASTER_WIFI, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node bt = { + .name = "bt", + .id = MASTER_BT, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node netsys = { + .name = "netsys", + .id = MASTER_NETSYS, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node dbgif = { + .name = "dbgif", + .id = MASTER_DBGIF, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node hrt_ddr_emi = { + .name = "hrt-ddr-emi", + .id = SLAVE_HRT_DDR_EMI, + .ep = 2, +}; + +static struct mtk_icc_node hrt_mmsys = { + .name = "hrt-mmsys", + .id = MASTER_HRT_MMSYS, + .ep = 0, + .num_links = 1, + .links = { SLAVE_HRT_DDR_EMI } +}; + +static struct mtk_icc_node hrt_mm_disp = { + .name = "hrt-mm-disp", + .id = MASTER_HRT_MM_DISP, + .ep = 0, + .num_links = 1, + .links = { MASTER_HRT_MMSYS } +}; + +static struct mtk_icc_node hrt_mm_vdec = { + .name = "hrt-mm-vdec", + .id = MASTER_HRT_MM_VDEC, + .ep = 0, + .num_links = 1, + .links = { MASTER_HRT_MMSYS } +}; + +static struct mtk_icc_node hrt_mm_venc = { + .name = "hrt-mm-venc", + .id = MASTER_HRT_MM_VENC, + .ep = 0, + .num_links = 1, + .links = { MASTER_HRT_MMSYS } +}; + +static struct mtk_icc_node hrt_mm_cam = { + .name = "hrt-mm-cam", + .id = MASTER_HRT_MM_CAM, + .ep = 0, + .num_links = 1, + .links = { MASTER_HRT_MMSYS } +}; + +static struct mtk_icc_node hrt_mm_img = { + .name = "hrt-mm-img", + .id = MASTER_HRT_MM_IMG, + .ep = 0, + .num_links = 1, + .links = { MASTER_HRT_MMSYS } +}; + +static struct mtk_icc_node hrt_mm_mdp = { + .name = "hrt-mm-mdp", + .id = MASTER_HRT_MM_MDP, + .ep = 0, + .num_links = 1, + .links = { MASTER_HRT_MMSYS } +}; + +static struct mtk_icc_node hrt_adsp = { + .name = "hrt-adsp", + .id = MASTER_HRT_ADSP, + .ep = 0, + .num_links = 1, + .links = { SLAVE_HRT_DDR_EMI } +}; + +static struct mtk_icc_node hrt_dbgif = { + .name = "hrt-dbgif", + .id = MASTER_HRT_DBGIF, + .ep = 0, + .num_links = 1, + .links = { SLAVE_HRT_DDR_EMI } +}; + +static struct mtk_icc_node *mt8196_emi_icc_nodes[] = { + [SLAVE_DDR_EMI] = &ddr_emi, + [MASTER_MCUSYS] = &mcusys, + [MASTER_MCU_0] = &mcu_port0, + [MASTER_MCU_1] = &mcu_port1, + [MASTER_MCU_2] = &mcu_port2, + [MASTER_MCU_3] = &mcu_port3, + [MASTER_MCU_4] = &mcu_port4, + [MASTER_GPUSYS] = &gpu, + [MASTER_MMSYS] = &mmsys, + [MASTER_MM_VPU] = &mm_vpu, + [MASTER_MM_DISP] = &mm_disp, + [MASTER_MM_VDEC] = &mm_vdec, + [MASTER_MM_VENC] = &mm_venc, + [MASTER_MM_CAM] = &mm_cam, + [MASTER_MM_IMG] = &mm_img, + [MASTER_MM_MDP] = &mm_mdp, + [MASTER_VPUSYS] = &vpusys, + [MASTER_VPU_0] = &vpu_port0, + [MASTER_VPU_1] = &vpu_port1, + [MASTER_MDLASYS] = &mdlasys, + [MASTER_MDLA_0] = &mdla_port0, + [MASTER_UFS] = &ufs, + [MASTER_PCIE] = &pcie, + [MASTER_USB] = &usb, + [MASTER_WIFI] = &wifi, + [MASTER_BT] = &bt, + [MASTER_NETSYS] = &netsys, + [MASTER_DBGIF] = &dbgif, + [SLAVE_HRT_DDR_EMI] = &hrt_ddr_emi, + [MASTER_HRT_MMSYS] = &hrt_mmsys, + [MASTER_HRT_MM_DISP] = &hrt_mm_disp, + [MASTER_HRT_MM_VDEC] = &hrt_mm_vdec, + [MASTER_HRT_MM_VENC] = &hrt_mm_venc, + [MASTER_HRT_MM_CAM] = &hrt_mm_cam, + [MASTER_HRT_MM_IMG] = &hrt_mm_img, + [MASTER_HRT_MM_MDP] = &hrt_mm_mdp, + [MASTER_HRT_ADSP] = &hrt_adsp, + [MASTER_HRT_DBGIF] = &hrt_dbgif +}; + +static struct mtk_icc_desc mt8196_emi_icc = { + .nodes = mt8196_emi_icc_nodes, + .num_nodes = ARRAY_SIZE(mt8196_emi_icc_nodes), +}; + +static const struct of_device_id mtk_mt8196_emi_icc_of_match[] = { + { .compatible = "mediatek,mt8196-emi", .data = &mt8196_emi_icc }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mtk_mt8196_emi_icc_of_match); + +static struct platform_driver mtk_emi_icc_mt8196_driver = { + .driver = { + .name = "emi-icc-mt8196", + .of_match_table = mtk_mt8196_emi_icc_of_match, + .sync_state = icc_sync_state, + }, + .probe = mtk_emi_icc_probe, + .remove = mtk_emi_icc_remove, + +}; +module_platform_driver(mtk_emi_icc_mt8196_driver); + +MODULE_AUTHOR("AngeloGioacchino Del Regno "); +MODULE_DESCRIPTION("MediaTek MT8196 EMI ICC driver"); +MODULE_LICENSE("GPL"); From 510f8214440c553e81774c5822437ccf154e9e38 Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Mon, 24 Nov 2025 12:07:01 +0100 Subject: [PATCH 041/297] interconnect: mediatek: Don't hijack parent device If the intention is that users of the interconnect declare their relationship to the child icc_emi node of the dvfsrc controller, then this code never worked. That's because it uses the parent dvfsrc device as the device it passes to the interconnect core framework, which means all the OF parsing is broken. Use the actual device instead, and pass the dvfsrc parent into the dvfsrc calls. Fixes: b45293799f75 ("interconnect: mediatek: Add MediaTek MT8183/8195 EMI Interconnect driver") Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Nicolas Frattaroli Link: https://lore.kernel.org/r/20251124-mt8196-dvfsrc-v2-12-d9c1334db9f3@collabora.com Signed-off-by: Georgi Djakov --- drivers/interconnect/mediatek/icc-emi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/interconnect/mediatek/icc-emi.c b/drivers/interconnect/mediatek/icc-emi.c index 7da740b5fa8d..182aa2b0623a 100644 --- a/drivers/interconnect/mediatek/icc-emi.c +++ b/drivers/interconnect/mediatek/icc-emi.c @@ -40,7 +40,7 @@ static int mtk_emi_icc_set(struct icc_node *src, struct icc_node *dst) if (unlikely(!src->provider)) return -EINVAL; - dev = src->provider->dev; + dev = src->provider->dev->parent; switch (node->ep) { case 0: @@ -97,7 +97,7 @@ int mtk_emi_icc_probe(struct platform_device *pdev) if (!data) return -ENOMEM; - provider->dev = pdev->dev.parent; + provider->dev = dev; provider->set = mtk_emi_icc_set; provider->aggregate = mtk_emi_icc_aggregate; provider->xlate = of_icc_xlate_onecell; From 6ffd02b82243d9907b5f5d2c7a2fc6a62669eece Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Mon, 24 Nov 2025 12:07:02 +0100 Subject: [PATCH 042/297] interconnect: mediatek: Aggregate bandwidth with saturating add By using a regular non-overflow-checking add, the MediaTek icc-emi driver will happy wrap at U32_MAX + 1 to 0. As it's common for the interconnect core to fill in INT_MAX values, this is not a hypothetical situation, but something that actually happens in regular use. This would be pretty disasterous if anything used this driver. Replace the addition with an overflow-checked addition from overflow.h, and saturate to U32_MAX if an overflow is detected. Fixes: b45293799f75 ("interconnect: mediatek: Add MediaTek MT8183/8195 EMI Interconnect driver") Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Nicolas Frattaroli Link: https://lore.kernel.org/r/20251124-mt8196-dvfsrc-v2-13-d9c1334db9f3@collabora.com Signed-off-by: Georgi Djakov --- drivers/interconnect/mediatek/icc-emi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/interconnect/mediatek/icc-emi.c b/drivers/interconnect/mediatek/icc-emi.c index 182aa2b0623a..dfa3a9cd9399 100644 --- a/drivers/interconnect/mediatek/icc-emi.c +++ b/drivers/interconnect/mediatek/icc-emi.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -22,7 +23,9 @@ static int mtk_emi_icc_aggregate(struct icc_node *node, u32 tag, u32 avg_bw, { struct mtk_icc_node *in = node->data; - *agg_avg += avg_bw; + if (check_add_overflow(*agg_avg, avg_bw, agg_avg)) + *agg_avg = U32_MAX; + *agg_peak = max_t(u32, *agg_peak, peak_bw); in->sum_avg = *agg_avg; From 0c1316b9521af0801e2502f390a89df5459094ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Fri, 19 Dec 2025 15:28:15 +0000 Subject: [PATCH 043/297] iio: buffer-dma: Fix coding style complains MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just making sure checkpatch is happy. No functional change intended. Signed-off-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/buffer/industrialio-buffer-dma.c | 24 +++++++++----------- include/linux/iio/buffer-dma.h | 16 ++++++++----- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/drivers/iio/buffer/industrialio-buffer-dma.c b/drivers/iio/buffer/industrialio-buffer-dma.c index ed187bb23708..1c94b334f987 100644 --- a/drivers/iio/buffer/industrialio-buffer-dma.c +++ b/drivers/iio/buffer/industrialio-buffer-dma.c @@ -169,8 +169,9 @@ static struct iio_dma_buffer_queue *iio_buffer_to_queue(struct iio_buffer *buf) return container_of(buf, struct iio_dma_buffer_queue, buffer); } -static struct iio_dma_buffer_block *iio_dma_buffer_alloc_block( - struct iio_dma_buffer_queue *queue, size_t size, bool fileio) +static struct iio_dma_buffer_block * +iio_dma_buffer_alloc_block(struct iio_dma_buffer_queue *queue, size_t size, + bool fileio) { struct iio_dma_buffer_block *block __free(kfree) = kzalloc(sizeof(*block), GFP_KERNEL); @@ -254,7 +255,7 @@ EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_block_done, "IIO_DMA_BUFFER"); * hand the blocks back to the queue. */ void iio_dma_buffer_block_list_abort(struct iio_dma_buffer_queue *queue, - struct list_head *list) + struct list_head *list) { struct iio_dma_buffer_block *block, *_block; bool cookie; @@ -434,7 +435,7 @@ static void iio_dma_buffer_fileio_free(struct iio_dma_buffer_queue *queue) } static void iio_dma_buffer_submit_block(struct iio_dma_buffer_queue *queue, - struct iio_dma_buffer_block *block) + struct iio_dma_buffer_block *block) { int ret; @@ -478,8 +479,7 @@ static void iio_dma_buffer_submit_block(struct iio_dma_buffer_queue *queue, * * This will allocate the DMA buffers and start the DMA transfers. */ -int iio_dma_buffer_enable(struct iio_buffer *buffer, - struct iio_dev *indio_dev) +int iio_dma_buffer_enable(struct iio_buffer *buffer, struct iio_dev *indio_dev) { struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); struct iio_dma_buffer_block *block, *_block; @@ -503,8 +503,7 @@ EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_enable, "IIO_DMA_BUFFER"); * Needs to be called when the device that the buffer is attached to stops * sampling. Typically should be the iio_buffer_access_ops disable callback. */ -int iio_dma_buffer_disable(struct iio_buffer *buffer, - struct iio_dev *indio_dev) +int iio_dma_buffer_disable(struct iio_buffer *buffer, struct iio_dev *indio_dev) { struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); @@ -519,7 +518,7 @@ int iio_dma_buffer_disable(struct iio_buffer *buffer, EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_disable, "IIO_DMA_BUFFER"); static void iio_dma_buffer_enqueue(struct iio_dma_buffer_queue *queue, - struct iio_dma_buffer_block *block) + struct iio_dma_buffer_block *block) { if (block->state == IIO_BLOCK_STATE_DEAD) { iio_buffer_block_put(block); @@ -531,8 +530,8 @@ static void iio_dma_buffer_enqueue(struct iio_dma_buffer_queue *queue, } } -static struct iio_dma_buffer_block *iio_dma_buffer_dequeue( - struct iio_dma_buffer_queue *queue) +static struct iio_dma_buffer_block * +iio_dma_buffer_dequeue(struct iio_dma_buffer_queue *queue) { struct iio_dma_buffer_block *block; unsigned int idx; @@ -661,8 +660,7 @@ size_t iio_dma_buffer_usage(struct iio_buffer *buf) for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { block = queue->fileio.blocks[i]; - if (block != queue->fileio.active_block - && block->state == IIO_BLOCK_STATE_DONE) + if (block != queue->fileio.active_block && block->state == IIO_BLOCK_STATE_DONE) data_available += block->size; } diff --git a/include/linux/iio/buffer-dma.h b/include/linux/iio/buffer-dma.h index cd2ba4bb7501..ef8687a88b73 100644 --- a/include/linux/iio/buffer-dma.h +++ b/include/linux/iio/buffer-dma.h @@ -119,7 +119,12 @@ struct iio_dma_buffer_queue { struct device *dev; const struct iio_dma_buffer_ops *ops; + /* + * A mutex to protect accessing, configuring (eg: enqueuing DMA blocks) + * and do file IO on struct iio_dma_buffer_queue objects. + */ struct mutex lock; + /* A spin lock to protect adding/removing blocks to the queue list */ spinlock_t list_lock; struct list_head incoming; @@ -136,20 +141,19 @@ struct iio_dma_buffer_queue { */ struct iio_dma_buffer_ops { int (*submit)(struct iio_dma_buffer_queue *queue, - struct iio_dma_buffer_block *block); + struct iio_dma_buffer_block *block); void (*abort)(struct iio_dma_buffer_queue *queue); }; void iio_dma_buffer_block_done(struct iio_dma_buffer_block *block); void iio_dma_buffer_block_list_abort(struct iio_dma_buffer_queue *queue, - struct list_head *list); + struct list_head *list); -int iio_dma_buffer_enable(struct iio_buffer *buffer, - struct iio_dev *indio_dev); +int iio_dma_buffer_enable(struct iio_buffer *buffer, struct iio_dev *indio_dev); int iio_dma_buffer_disable(struct iio_buffer *buffer, - struct iio_dev *indio_dev); + struct iio_dev *indio_dev); int iio_dma_buffer_read(struct iio_buffer *buffer, size_t n, - char __user *user_buffer); + char __user *user_buffer); int iio_dma_buffer_write(struct iio_buffer *buffer, size_t n, const char __user *user_buffer); size_t iio_dma_buffer_usage(struct iio_buffer *buffer); From a0dcec6aa8ced73edf9c72a853218d56035770ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Fri, 19 Dec 2025 15:28:16 +0000 Subject: [PATCH 044/297] iio: buffer-dmaengine: Use the cleanup.h API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make use of the cleanup.h API for locks in order to simplify some code paths. Signed-off-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/buffer/industrialio-buffer-dmaengine.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c index 27dd56334345..750bd3f3fd4a 100644 --- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -49,11 +50,9 @@ static void iio_dmaengine_buffer_block_done(void *data, const struct dmaengine_result *result) { struct iio_dma_buffer_block *block = data; - unsigned long flags; - spin_lock_irqsave(&block->queue->list_lock, flags); - list_del(&block->head); - spin_unlock_irqrestore(&block->queue->list_lock, flags); + scoped_guard(spinlock_irqsave, &block->queue->list_lock) + list_del(&block->head); block->bytes_used -= result->residue; iio_dma_buffer_block_done(block); } @@ -131,9 +130,8 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue, if (dma_submit_error(cookie)) return dma_submit_error(cookie); - spin_lock_irq(&dmaengine_buffer->queue.list_lock); - list_add_tail(&block->head, &dmaengine_buffer->active); - spin_unlock_irq(&dmaengine_buffer->queue.list_lock); + scoped_guard(spinlock_irq, &dmaengine_buffer->queue.list_lock) + list_add_tail(&block->head, &dmaengine_buffer->active); dma_async_issue_pending(dmaengine_buffer->chan); From e358215e0a8e1a593c064ce35e4b116fb27de358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Fri, 19 Dec 2025 15:28:17 +0000 Subject: [PATCH 045/297] iio: buffer-dmaengine: Fix coding style complains MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just making sure checkpatch is happy. No functional change intended. Signed-off-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/buffer/industrialio-buffer-dmaengine.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c index 750bd3f3fd4a..0d3454f4adb0 100644 --- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c @@ -40,14 +40,13 @@ struct dmaengine_buffer { size_t max_size; }; -static struct dmaengine_buffer *iio_buffer_to_dmaengine_buffer( - struct iio_buffer *buffer) +static struct dmaengine_buffer *iio_buffer_to_dmaengine_buffer(struct iio_buffer *buffer) { return container_of(buffer, struct dmaengine_buffer, queue.buffer); } static void iio_dmaengine_buffer_block_done(void *data, - const struct dmaengine_result *result) + const struct dmaengine_result *result) { struct iio_dma_buffer_block *block = data; @@ -58,7 +57,7 @@ static void iio_dmaengine_buffer_block_done(void *data, } static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue, - struct iio_dma_buffer_block *block) + struct iio_dma_buffer_block *block) { struct dmaengine_buffer *dmaengine_buffer = iio_buffer_to_dmaengine_buffer(&queue->buffer); @@ -187,7 +186,7 @@ static const struct iio_dma_buffer_ops iio_dmaengine_default_ops = { }; static ssize_t iio_dmaengine_buffer_get_length_align(struct device *dev, - struct device_attribute *attr, char *buf) + struct device_attribute *attr, char *buf) { struct iio_buffer *buffer = to_iio_dev_attr(attr)->buffer; struct dmaengine_buffer *dmaengine_buffer = @@ -246,7 +245,7 @@ static struct iio_buffer *iio_dmaengine_buffer_alloc(struct dma_chan *chan) dmaengine_buffer->max_size = dma_get_max_seg_size(chan->device->dev); iio_dma_buffer_init(&dmaengine_buffer->queue, chan->device->dev, - &iio_dmaengine_default_ops); + &iio_dmaengine_default_ops); dmaengine_buffer->queue.buffer.attrs = iio_dmaengine_buffer_attrs; dmaengine_buffer->queue.buffer.access = &iio_dmaengine_buffer_ops; From dcc3ac29f46fff6775c0a945afc68fd9dece6d34 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 19 Dec 2025 15:31:50 +0100 Subject: [PATCH 046/297] iio: adc: aspeed: Simplify with dev_err_probe Use dev_err_probe() to make error code handling simpler and handle deferred probe nicely (avoid spamming logs). Signed-off-by: Krzysztof Kozlowski Signed-off-by: Jonathan Cameron --- drivers/iio/adc/aspeed_adc.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/iio/adc/aspeed_adc.c b/drivers/iio/adc/aspeed_adc.c index bf2bfd6bdc41..1ae45fe90e6c 100644 --- a/drivers/iio/adc/aspeed_adc.c +++ b/drivers/iio/adc/aspeed_adc.c @@ -535,11 +535,10 @@ static int aspeed_adc_probe(struct platform_device *pdev) return PTR_ERR(data->clk_scaler); data->rst = devm_reset_control_get_shared(&pdev->dev, NULL); - if (IS_ERR(data->rst)) { - dev_err(&pdev->dev, - "invalid or missing reset controller device tree entry"); - return PTR_ERR(data->rst); - } + if (IS_ERR(data->rst)) + return dev_err_probe(&pdev->dev, PTR_ERR(data->rst), + "invalid or missing reset controller device tree entry"); + reset_control_deassert(data->rst); ret = devm_add_action_or_reset(data->dev, aspeed_adc_reset_assert, From 0dce3f98a77fec3cd390d655d97cbed1ff23d539 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 19 Dec 2025 15:31:51 +0100 Subject: [PATCH 047/297] iio: adc: exynos: Simplify with dev_err_probe Use dev_err_probe() to make error code handling simpler and handle deferred probe nicely (avoid spamming logs). Signed-off-by: Krzysztof Kozlowski Signed-off-by: Jonathan Cameron --- drivers/iio/adc/exynos_adc.c | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/drivers/iio/adc/exynos_adc.c b/drivers/iio/adc/exynos_adc.c index 1484adff00df..491e8dcfd91e 100644 --- a/drivers/iio/adc/exynos_adc.c +++ b/drivers/iio/adc/exynos_adc.c @@ -564,10 +564,8 @@ static int exynos_adc_probe(struct platform_device *pdev) info = iio_priv(indio_dev); info->data = exynos_adc_get_data(pdev); - if (!info->data) { - dev_err(&pdev->dev, "failed getting exynos_adc_data\n"); - return -EINVAL; - } + if (!info->data) + return dev_err_probe(&pdev->dev, -EINVAL, "failed getting exynos_adc_data\n"); info->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(info->regs)) @@ -578,10 +576,9 @@ static int exynos_adc_probe(struct platform_device *pdev) info->pmu_map = syscon_regmap_lookup_by_phandle( pdev->dev.of_node, "samsung,syscon-phandle"); - if (IS_ERR(info->pmu_map)) { - dev_err(&pdev->dev, "syscon regmap lookup failed.\n"); - return PTR_ERR(info->pmu_map); - } + if (IS_ERR(info->pmu_map)) + return dev_err_probe(&pdev->dev, PTR_ERR(info->pmu_map), + "syscon regmap lookup failed.\n"); } irq = platform_get_irq(pdev, 0); @@ -593,20 +590,14 @@ static int exynos_adc_probe(struct platform_device *pdev) init_completion(&info->completion); info->clk = devm_clk_get(&pdev->dev, "adc"); - if (IS_ERR(info->clk)) { - dev_err(&pdev->dev, "failed getting clock, err = %ld\n", - PTR_ERR(info->clk)); - return PTR_ERR(info->clk); - } + if (IS_ERR(info->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(info->clk), "failed getting clock\n"); if (info->data->needs_sclk) { info->sclk = devm_clk_get(&pdev->dev, "sclk"); - if (IS_ERR(info->sclk)) { - dev_err(&pdev->dev, - "failed getting sclk clock, err = %ld\n", - PTR_ERR(info->sclk)); - return PTR_ERR(info->sclk); - } + if (IS_ERR(info->sclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(info->sclk), + "failed getting sclk clock\n"); } info->vdd = devm_regulator_get(&pdev->dev, "vdd"); From f8831384dc8b79d757a3ea0dd7c1dbeb10c3e26f Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 19 Dec 2025 15:31:52 +0100 Subject: [PATCH 048/297] iio: adc: qcom-spmi-rradc: Simplify with dev_err_probe Use dev_err_probe() to make error code handling simpler. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Konrad Dybcio Signed-off-by: Jonathan Cameron --- drivers/iio/adc/qcom-spmi-rradc.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/drivers/iio/adc/qcom-spmi-rradc.c b/drivers/iio/adc/qcom-spmi-rradc.c index b245416bae12..8e75665204d1 100644 --- a/drivers/iio/adc/qcom-spmi-rradc.c +++ b/drivers/iio/adc/qcom-spmi-rradc.c @@ -934,20 +934,15 @@ static int rradc_probe(struct platform_device *pdev) chip = iio_priv(indio_dev); chip->regmap = dev_get_regmap(pdev->dev.parent, NULL); - if (!chip->regmap) { - dev_err(dev, "Couldn't get parent's regmap\n"); - return -EINVAL; - } + if (!chip->regmap) + return dev_err_probe(dev, -EINVAL, "Couldn't get parent's regmap\n"); chip->dev = dev; mutex_init(&chip->conversion_lock); ret = device_property_read_u32(dev, "reg", &chip->base); - if (ret < 0) { - dev_err(chip->dev, "Couldn't find reg address, ret = %d\n", - ret); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Couldn't find reg address\n"); batt_id_delay = -1; ret = device_property_read_u32(dev, "qcom,batt-id-delay-ms", @@ -975,10 +970,9 @@ static int rradc_probe(struct platform_device *pdev) /* Get the PMIC revision, we need it to handle some varying coefficients */ chip->pmic = qcom_pmic_get(chip->dev); - if (IS_ERR(chip->pmic)) { - dev_err(chip->dev, "Unable to get reference to PMIC device\n"); - return PTR_ERR(chip->pmic); - } + if (IS_ERR(chip->pmic)) + return dev_err_probe(dev, PTR_ERR(chip->pmic), + "Unable to get reference to PMIC device\n"); switch (chip->pmic->subtype) { case PMI8998_SUBTYPE: From df2a034aef258ec39524e8fd14440c1b8ce622ff Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 19 Dec 2025 15:31:53 +0100 Subject: [PATCH 049/297] iio: adc: rockchip: Simplify with dev_err_probe Use dev_err_probe() to make error code handling simpler and handle deferred probe nicely (avoid spamming logs). Signed-off-by: Krzysztof Kozlowski Reviewed-by: Shawn Lin Signed-off-by: Jonathan Cameron --- drivers/iio/adc/rockchip_saradc.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/iio/adc/rockchip_saradc.c b/drivers/iio/adc/rockchip_saradc.c index 6721da0ed7bb..263d80c5fc50 100644 --- a/drivers/iio/adc/rockchip_saradc.c +++ b/drivers/iio/adc/rockchip_saradc.c @@ -492,10 +492,9 @@ static int rockchip_saradc_probe(struct platform_device *pdev) */ info->reset = devm_reset_control_get_optional_exclusive(&pdev->dev, "saradc-apb"); - if (IS_ERR(info->reset)) { - ret = PTR_ERR(info->reset); - return dev_err_probe(&pdev->dev, ret, "failed to get saradc-apb\n"); - } + if (IS_ERR(info->reset)) + return dev_err_probe(&pdev->dev, PTR_ERR(info->reset), + "failed to get saradc-apb\n"); init_completion(&info->completion); @@ -505,10 +504,8 @@ static int rockchip_saradc_probe(struct platform_device *pdev) ret = devm_request_irq(&pdev->dev, irq, rockchip_saradc_isr, 0, dev_name(&pdev->dev), info); - if (ret < 0) { - dev_err(&pdev->dev, "failed requesting irq %d\n", irq); - return ret; - } + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "failed requesting irq %d\n", irq); info->vref = devm_regulator_get(&pdev->dev, "vref"); if (IS_ERR(info->vref)) From 4ea6e9b507afee522dc2e543400c3949b9d16d7d Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 19 Dec 2025 15:31:54 +0100 Subject: [PATCH 050/297] iio: adc: sc27xx: Simplify with dev_err_probe Use dev_err_probe() to make error code handling simpler and handle deferred probe nicely (avoid spamming logs). Signed-off-by: Krzysztof Kozlowski Signed-off-by: Jonathan Cameron --- drivers/iio/adc/sc27xx_adc.c | 49 +++++++++++++----------------------- 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/drivers/iio/adc/sc27xx_adc.c b/drivers/iio/adc/sc27xx_adc.c index 2535c2c3e60b..6209499c5c37 100644 --- a/drivers/iio/adc/sc27xx_adc.c +++ b/drivers/iio/adc/sc27xx_adc.c @@ -867,10 +867,8 @@ static int sc27xx_adc_probe(struct platform_device *pdev) int ret; pdata = of_device_get_match_data(dev); - if (!pdata) { - dev_err(dev, "No matching driver data found\n"); - return -EINVAL; - } + if (!pdata) + return dev_err_probe(dev, -EINVAL, "No matching driver data found\n"); indio_dev = devm_iio_device_alloc(dev, sizeof(*sc27xx_data)); if (!indio_dev) @@ -879,56 +877,43 @@ static int sc27xx_adc_probe(struct platform_device *pdev) sc27xx_data = iio_priv(indio_dev); sc27xx_data->regmap = dev_get_regmap(dev->parent, NULL); - if (!sc27xx_data->regmap) { - dev_err(dev, "failed to get ADC regmap\n"); - return -ENODEV; - } + if (!sc27xx_data->regmap) + return dev_err_probe(dev, -ENODEV, "failed to get ADC regmap\n"); ret = of_property_read_u32(np, "reg", &sc27xx_data->base); - if (ret) { - dev_err(dev, "failed to get ADC base address\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to get ADC base address\n"); sc27xx_data->irq = platform_get_irq(pdev, 0); if (sc27xx_data->irq < 0) return sc27xx_data->irq; ret = of_hwspin_lock_get_id(np, 0); - if (ret < 0) { - dev_err(dev, "failed to get hwspinlock id\n"); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get hwspinlock id\n"); sc27xx_data->hwlock = devm_hwspin_lock_request_specific(dev, ret); - if (!sc27xx_data->hwlock) { - dev_err(dev, "failed to request hwspinlock\n"); - return -ENXIO; - } + if (!sc27xx_data->hwlock) + return dev_err_probe(dev, -ENXIO, "failed to request hwspinlock\n"); sc27xx_data->dev = dev; if (pdata->set_volref) { sc27xx_data->volref = devm_regulator_get(dev, "vref"); - if (IS_ERR(sc27xx_data->volref)) { - ret = PTR_ERR(sc27xx_data->volref); - return dev_err_probe(dev, ret, "failed to get ADC volref\n"); - } + if (IS_ERR(sc27xx_data->volref)) + return dev_err_probe(dev, PTR_ERR(sc27xx_data->volref), + "failed to get ADC volref\n"); } sc27xx_data->var_data = pdata; sc27xx_data->var_data->init_scale(sc27xx_data); ret = sc27xx_adc_enable(sc27xx_data); - if (ret) { - dev_err(dev, "failed to enable ADC module\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to enable ADC module\n"); ret = devm_add_action_or_reset(dev, sc27xx_adc_disable, sc27xx_data); - if (ret) { - dev_err(dev, "failed to add ADC disable action\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to add ADC disable action\n"); indio_dev->name = dev_name(dev); indio_dev->modes = INDIO_DIRECT_MODE; From e7324980fa91f5bc0d1f5adc82d31775d4cab5bd Mon Sep 17 00:00:00 2001 From: Fiona Klute Date: Thu, 18 Dec 2025 16:12:05 +0100 Subject: [PATCH 051/297] iio: chemical: scd4x: expose timestamp channel Timestamps were already written to the buffer in scd4x_trigger_handler(), this patch makes them available as a channel. Signed-off-by: Fiona Klute Signed-off-by: Jonathan Cameron --- drivers/iio/chemical/scd4x.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/iio/chemical/scd4x.c b/drivers/iio/chemical/scd4x.c index 8859f89fb2a9..bb0019bb02a1 100644 --- a/drivers/iio/chemical/scd4x.c +++ b/drivers/iio/chemical/scd4x.c @@ -59,6 +59,8 @@ enum scd4x_channel_idx { SCD4X_CO2, SCD4X_TEMP, SCD4X_HR, + /* kernel timestamp, at the end of buffer */ + SCD4X_TS, }; struct scd4x_state { @@ -615,6 +617,7 @@ static const struct iio_chan_spec scd4x_channels[] = { .endianness = IIO_BE, }, }, + IIO_CHAN_SOFT_TIMESTAMP(SCD4X_TS), }; static int scd4x_suspend(struct device *dev) From 676cc11b70221671b7257c90064ada4192d7baed Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Thu, 18 Dec 2025 13:05:43 +0200 Subject: [PATCH 052/297] iio: pressure: mprls0025pa: Kconfig allow bus selection Allow the user to select either the SPI or the i2c bus specific module and autoselect core if needed. Signed-off-by: Petre Rodan Reviewed-by: Marcelo Schmitt Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/Kconfig | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/drivers/iio/pressure/Kconfig b/drivers/iio/pressure/Kconfig index b2720be51506..0d25842aa414 100644 --- a/drivers/iio/pressure/Kconfig +++ b/drivers/iio/pressure/Kconfig @@ -216,29 +216,33 @@ config MPL3115 will be called mpl3115. config MPRLS0025PA - tristate "Honeywell MPRLS0025PA (MicroPressure sensors series)" - depends on (I2C || SPI_MASTER) - select MPRLS0025PA_I2C if I2C - select MPRLS0025PA_SPI if SPI_MASTER + tristate select IIO_BUFFER select IIO_TRIGGERED_BUFFER - help - Say Y here to build support for the Honeywell MicroPressure pressure - sensor series. There are many different types with different pressure - range. These ranges can be set up in the device tree. - - To compile this driver as a module, choose M here: the module will be - called mprls0025pa. config MPRLS0025PA_I2C - tristate - depends on MPRLS0025PA + tristate "Honeywell MPR pressure sensor series I2C driver" depends on I2C + select MPRLS0025PA + help + Say Y here to build I2C bus support for the Honeywell MicroPressure + series sensor. + + To compile this driver as a module, choose M here: the module + will be called mprls0025pa_i2c and you will also get mprls0025pa + for the core module. config MPRLS0025PA_SPI - tristate - depends on MPRLS0025PA + tristate "Honeywell MPR pressure sensor series SPI driver" depends on SPI_MASTER + select MPRLS0025PA + help + Say Y here to build SPI bus support for the Honeywell MicroPressure + series sensor. + + To compile this driver as a module, choose M here: the module + will be called mprls0025pa_spi and you will also get mprls0025pa + for the core module. config MS5611 tristate "Measurement Specialties MS5611 pressure sensor driver" From e23f687c0d81802b54b25c95f23e299ddf0ba28e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Maneyrol Date: Thu, 18 Dec 2025 10:21:17 +0100 Subject: [PATCH 053/297] iio: imu: inv_icm42600: enable temp polling when buffer is on Delete iio_device_claim_direct_mode() when reading temperature. It enables polling of temperature data while buffer is on and it doesn't have any impact on the other sensors. Signed-off-by: Jean-Baptiste Maneyrol Signed-off-by: Jonathan Cameron --- drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c index 30f6a9595eea..727b03d541a5 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c @@ -59,10 +59,7 @@ int inv_icm42600_temp_read_raw(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_RAW: - if (!iio_device_claim_direct(indio_dev)) - return -EBUSY; ret = inv_icm42600_temp_read(st, &temp); - iio_device_release_direct(indio_dev); if (ret) return ret; *val = temp; From 43fabbb9249f4e609ba15533f49c3738344ed801 Mon Sep 17 00:00:00 2001 From: Kurt Borja Date: Thu, 11 Dec 2025 23:25:43 -0500 Subject: [PATCH 054/297] dt-bindings: iio: adc: Add TI ADS1018/ADS1118 Add documentation for Texas Instruments ADS1018 and ADS1118 analog-to-digital converters. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Kurt Borja Reviewed-by: David Lechner Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/ti,ads1018.yaml | 82 +++++++++++++++++++ MAINTAINERS | 6 ++ 2 files changed, 88 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml new file mode 100644 index 000000000000..81ee024be2e3 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/ti,ads1018.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI ADS1018/ADS1118 SPI analog to digital converter + +maintainers: + - Kurt Borja + +description: | + The ADS1018/ADS1118 is a precision, low-power, 12-bit/16-bit, analog to + digital converter (ADC). It integrates a programmable gain amplifier (PGA), + internal voltage reference, oscillator and high-accuracy temperature sensor. + + Datasheets: + - ADS1018: https://www.ti.com/lit/ds/symlink/ads1018.pdf + - ADS1118: https://www.ti.com/lit/ds/symlink/ads1118.pdf + +properties: + compatible: + enum: + - ti,ads1018 + - ti,ads1118 + + reg: + maxItems: 1 + + vdd-supply: true + + spi-max-frequency: + maximum: 4000000 + + spi-cpha: true + + interrupts: + description: DOUT/DRDY (Data Out/Data Ready) line. + maxItems: 1 + + drdy-gpios: + description: + Extra GPIO line connected to DOUT/DRDY (Data Out/Data Ready). This allows + distinguishing between interrupts triggered by the data-ready signal and + interrupts triggered by an SPI transfer. + maxItems: 1 + + '#io-channel-cells': + const: 1 + +required: + - compatible + - reg + - vdd-supply + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + #include + + spi { + #address-cells = <1>; + #size-cells = <0>; + + adc@0 { + compatible = "ti,ads1118"; + reg = <0>; + + spi-max-frequency = <4000000>; + spi-cpha; + + vdd-supply = <&vdd_3v3_reg>; + + interrupts-extended = <&gpio 14 IRQ_TYPE_EDGE_FALLING>; + drdy-gpios = <&gpio 14 GPIO_ACTIVE_LOW>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index dc6936ba0ab9..7c3c5b9eb6b7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26017,6 +26017,12 @@ S: Maintained F: Documentation/devicetree/bindings/iio/adc/ti,ads1119.yaml F: drivers/iio/adc/ti-ads1119.c +TI ADS1018 ADC DRIVER +M: Kurt Borja +L: linux-iio@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml + TI ADS7924 ADC DRIVER M: Hugo Villeneuve L: linux-iio@vger.kernel.org From bf0bba486b5bd5e2d6100ed7dd1e38e0304ba40f Mon Sep 17 00:00:00 2001 From: Kurt Borja Date: Thu, 11 Dec 2025 23:25:44 -0500 Subject: [PATCH 055/297] iio: adc: Add ti-ads1018 driver Add ti-ads1018 driver for Texas Instruments ADS1018 and ADS1118 SPI analog-to-digital converters. This chips' MOSI pin is shared with a data-ready interrupt. Defining this interrupt in devicetree is optional, therefore we only create an IIO trigger if one is found. Handling this interrupt requires some considerations. When enabling the trigger the CS line is tied low (active), thus we need to hold spi_bus_lock() too, to avoid state corruption. This is done inside the set_trigger_state() callback, to let users use other triggers without wasting a bus lock. Reviewed-by: Andy Shevchenko Signed-off-by: Kurt Borja Reviewed-by: David Lechner Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 12 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ti-ads1018.c | 740 +++++++++++++++++++++++++++++++++++ 4 files changed, 754 insertions(+) create mode 100644 drivers/iio/adc/ti-ads1018.c diff --git a/MAINTAINERS b/MAINTAINERS index 7c3c5b9eb6b7..9075de044d18 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26022,6 +26022,7 @@ M: Kurt Borja L: linux-iio@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml +F: drivers/iio/adc/ti-ads1018.c TI ADS7924 ADC DRIVER M: Hugo Villeneuve diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 48e560b7b851..576480e3ac4a 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1676,6 +1676,18 @@ config TI_ADS1015 This driver can also be built as a module. If so, the module will be called ti-ads1015. +config TI_ADS1018 + tristate "Texas Instruments ADS1018 ADC" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for Texas Instruments ADS1018 and + ADS1118 ADC chips. + + This driver can also be built as a module. If so, the module will be + called ti-ads1018. + config TI_ADS1100 tristate "Texas Instruments ADS1100 and ADS1000 ADC" depends on I2C diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 05b01c00429f..b1b1d5a2273f 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -146,6 +146,7 @@ obj-$(CONFIG_TI_ADC12138) += ti-adc12138.o obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o obj-$(CONFIG_TI_ADC161S626) += ti-adc161s626.o obj-$(CONFIG_TI_ADS1015) += ti-ads1015.o +obj-$(CONFIG_TI_ADS1018) += ti-ads1018.o obj-$(CONFIG_TI_ADS1100) += ti-ads1100.o obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o diff --git a/drivers/iio/adc/ti-ads1018.c b/drivers/iio/adc/ti-ads1018.c new file mode 100644 index 000000000000..286e06dc70b8 --- /dev/null +++ b/drivers/iio/adc/ti-ads1018.c @@ -0,0 +1,740 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Texas Instruments ADS1018 ADC driver + * + * Copyright (C) 2025 Kurt Borja + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#define ADS1018_CFG_OS_TRIG BIT(15) +#define ADS1018_CFG_TS_MODE_EN BIT(4) +#define ADS1018_CFG_PULL_UP BIT(3) +#define ADS1018_CFG_NOP BIT(1) +#define ADS1018_CFG_VALID (ADS1018_CFG_PULL_UP | ADS1018_CFG_NOP) + +#define ADS1018_CFG_MUX_MASK GENMASK(14, 12) + +#define ADS1018_CFG_PGA_MASK GENMASK(11, 9) +#define ADS1018_PGA_DEFAULT 2 + +#define ADS1018_CFG_MODE_MASK BIT(8) +#define ADS1018_MODE_CONTINUOUS 0 +#define ADS1018_MODE_ONESHOT 1 + +#define ADS1018_CFG_DRATE_MASK GENMASK(7, 5) +#define ADS1018_DRATE_DEFAULT 4 + +#define ADS1018_NUM_PGA_MODES 6 +#define ADS1018_CHANNELS_MAX 10 + +struct ads1018_chan_data { + u8 pga_mode; + u8 data_rate_mode; +}; + +struct ads1018_chip_info { + const char *name; + const struct iio_chan_spec *channels; + unsigned long num_channels; + + /* IIO_VAL_INT */ + const u32 *data_rate_mode_to_hz; + unsigned long num_data_rate_mode_to_hz; + + /* + * Let `res` be the chip's resolution and `fsr` (millivolts) be the + * full-scale range corresponding to the PGA mode given by the array + * index. Then, the gain is calculated using the following formula: + * + * gain = |fsr| / 2^(res - 1) + * + * This value then has to be represented in IIO_VAL_INT_PLUS_NANO + * format. For example if: + * + * gain = 6144 / 2^(16 - 1) = 0.1875 + * + * ...then the formatted value is: + * + * { 0, 187500000 } + */ + const u32 pga_mode_to_gain[ADS1018_NUM_PGA_MODES][2]; + + /* IIO_VAL_INT_PLUS_MICRO */ + const u32 temp_scale[2]; +}; + +struct ads1018 { + struct spi_device *spi; + struct iio_trigger *indio_trig; + + struct gpio_desc *drdy_gpiod; + int drdy_irq; + + struct ads1018_chan_data chan_data[ADS1018_CHANNELS_MAX]; + const struct ads1018_chip_info *chip_info; + + struct spi_message msg_read; + struct spi_transfer xfer; + __be16 tx_buf[2] __aligned(IIO_DMA_MINALIGN); + __be16 rx_buf[2]; +}; + +#define ADS1018_VOLT_DIFF_CHAN(_index, _chan, _chan2, _realbits) { \ + .type = IIO_VOLTAGE, \ + .channel = _chan, \ + .channel2 = _chan2, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = _realbits, \ + .storagebits = 16, \ + .shift = 16 - _realbits, \ + .endianness = IIO_BE, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .indexed = true, \ + .differential = true, \ +} + +#define ADS1018_VOLT_CHAN(_index, _chan, _realbits) { \ + .type = IIO_VOLTAGE, \ + .channel = _chan, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = _realbits, \ + .storagebits = 16, \ + .shift = 16 - _realbits, \ + .endianness = IIO_BE, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .indexed = true, \ +} + +#define ADS1018_TEMP_CHAN(_index, _realbits) { \ + .type = IIO_TEMP, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = _realbits, \ + .storagebits = 16, \ + .shift = 16 - _realbits, \ + .endianness = IIO_BE, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ +} + +static const struct iio_chan_spec ads1118_iio_channels[] = { + ADS1018_VOLT_DIFF_CHAN(0, 0, 1, 16), + ADS1018_VOLT_DIFF_CHAN(1, 0, 3, 16), + ADS1018_VOLT_DIFF_CHAN(2, 1, 3, 16), + ADS1018_VOLT_DIFF_CHAN(3, 2, 3, 16), + ADS1018_VOLT_CHAN(4, 0, 16), + ADS1018_VOLT_CHAN(5, 1, 16), + ADS1018_VOLT_CHAN(6, 2, 16), + ADS1018_VOLT_CHAN(7, 3, 16), + ADS1018_TEMP_CHAN(8, 14), + IIO_CHAN_SOFT_TIMESTAMP(9), +}; + +static const struct iio_chan_spec ads1018_iio_channels[] = { + ADS1018_VOLT_DIFF_CHAN(0, 0, 1, 12), + ADS1018_VOLT_DIFF_CHAN(1, 0, 3, 12), + ADS1018_VOLT_DIFF_CHAN(2, 1, 3, 12), + ADS1018_VOLT_DIFF_CHAN(3, 2, 3, 12), + ADS1018_VOLT_CHAN(4, 0, 12), + ADS1018_VOLT_CHAN(5, 1, 12), + ADS1018_VOLT_CHAN(6, 2, 12), + ADS1018_VOLT_CHAN(7, 3, 12), + ADS1018_TEMP_CHAN(8, 12), + IIO_CHAN_SOFT_TIMESTAMP(9), +}; + +/** + * ads1018_calc_delay - Calculates a suitable delay for a single-shot reading + * @hz: Sampling frequency + * + * Calculates an appropriate delay for a single shot reading given a sampling + * frequency. + * + * Return: Delay in microseconds (Always greater than 0). + */ +static u32 ads1018_calc_delay(unsigned int hz) +{ + /* + * Calculate the worst-case sampling rate by subtracting 10% error + * specified in the datasheet... + */ + hz -= DIV_ROUND_UP(hz, 10); + + /* ...Then calculate time per sample in microseconds. */ + return DIV_ROUND_UP(HZ_PER_MHZ, hz); +} + +/** + * ads1018_spi_read_exclusive - Reads a conversion value from the device + * @ads1018: Device data + * @cnv: ADC Conversion value (optional) + * @hold_cs: Keep CS line asserted after the SPI transfer + * + * Reads the most recent ADC conversion value, without updating the + * device's configuration. + * + * Context: Expects iio_device_claim_buffer_mode() is held and SPI bus + * *exclusive* use. + * + * Return: 0 on success, negative errno on error. + */ +static int ads1018_spi_read_exclusive(struct ads1018 *ads1018, __be16 *cnv, + bool hold_cs) +{ + int ret; + + ads1018->xfer.cs_change = hold_cs; + + ret = spi_sync_locked(ads1018->spi, &ads1018->msg_read); + if (ret) + return ret; + + if (cnv) + *cnv = ads1018->rx_buf[0]; + + return 0; +} + +/** + * ads1018_single_shot - Performs a one-shot reading sequence + * @ads1018: Device data + * @chan: Channel specification + * @cnv: Conversion value + * + * Writes a new configuration, waits an appropriate delay, then reads the most + * recent conversion. + * + * Context: Expects iio_device_claim_direct() is held. + * + * Return: 0 on success, negative errno on error. + */ +static int ads1018_single_shot(struct ads1018 *ads1018, + struct iio_chan_spec const *chan, u16 *cnv) +{ + u8 max_drate_mode = ads1018->chip_info->num_data_rate_mode_to_hz - 1; + u8 drate = ads1018->chip_info->data_rate_mode_to_hz[max_drate_mode]; + u8 pga_mode = ads1018->chan_data[chan->scan_index].pga_mode; + struct spi_transfer xfer[2] = { + { + .tx_buf = ads1018->tx_buf, + .len = sizeof(ads1018->tx_buf[0]), + .delay = { + .value = ads1018_calc_delay(drate), + .unit = SPI_DELAY_UNIT_USECS, + }, + .cs_change = 1, /* 16-bit mode requires CS de-assert */ + }, + { + .rx_buf = ads1018->rx_buf, + .len = sizeof(ads1018->rx_buf[0]), + }, + }; + u16 cfg; + int ret; + + cfg = ADS1018_CFG_VALID | ADS1018_CFG_OS_TRIG; + cfg |= FIELD_PREP(ADS1018_CFG_MUX_MASK, chan->scan_index); + cfg |= FIELD_PREP(ADS1018_CFG_PGA_MASK, pga_mode); + cfg |= FIELD_PREP(ADS1018_CFG_MODE_MASK, ADS1018_MODE_ONESHOT); + cfg |= FIELD_PREP(ADS1018_CFG_DRATE_MASK, max_drate_mode); + + if (chan->type == IIO_TEMP) + cfg |= ADS1018_CFG_TS_MODE_EN; + + ads1018->tx_buf[0] = cpu_to_be16(cfg); + ret = spi_sync_transfer(ads1018->spi, xfer, ARRAY_SIZE(xfer)); + if (ret) + return ret; + + *cnv = be16_to_cpu(ads1018->rx_buf[0]); + + return 0; +} + +static int +ads1018_read_raw_direct_mode(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + const struct ads1018_chip_info *chip_info = ads1018->chip_info; + u8 addr = chan->scan_index; + u8 pga_mode, drate_mode; + u16 cnv; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = ads1018_single_shot(ads1018, chan, &cnv); + if (ret) + return ret; + + cnv >>= chan->scan_type.shift; + *val = sign_extend32(cnv, chan->scan_type.realbits - 1); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + pga_mode = ads1018->chan_data[addr].pga_mode; + *val = chip_info->pga_mode_to_gain[pga_mode][0]; + *val2 = chip_info->pga_mode_to_gain[pga_mode][1]; + return IIO_VAL_INT_PLUS_NANO; + + case IIO_TEMP: + *val = chip_info->temp_scale[0]; + *val2 = chip_info->temp_scale[1]; + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EOPNOTSUPP; + } + + case IIO_CHAN_INFO_SAMP_FREQ: + drate_mode = ads1018->chan_data[addr].data_rate_mode; + *val = chip_info->data_rate_mode_to_hz[drate_mode]; + return IIO_VAL_INT; + + default: + return -EOPNOTSUPP; + } +} + +static int +ads1018_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ads1018_read_raw_direct_mode(indio_dev, chan, val, val2, mask); + iio_device_release_direct(indio_dev); + + return ret; +} + +static int +ads1018_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, long mask) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *type = IIO_VAL_INT_PLUS_NANO; + *vals = (const int *)ads1018->chip_info->pga_mode_to_gain; + *length = ADS1018_NUM_PGA_MODES * 2; + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT; + *vals = ads1018->chip_info->data_rate_mode_to_hz; + *length = ads1018->chip_info->num_data_rate_mode_to_hz; + return IIO_AVAIL_LIST; + + default: + return -EOPNOTSUPP; + } +} + +static int +ads1018_write_raw_direct_mode(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + const struct ads1018_chip_info *info = ads1018->chip_info; + unsigned int i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < ADS1018_NUM_PGA_MODES; i++) { + if (val == info->pga_mode_to_gain[i][0] && + val2 == info->pga_mode_to_gain[i][1]) + break; + } + if (i == ADS1018_NUM_PGA_MODES) + return -EINVAL; + + ads1018->chan_data[chan->scan_index].pga_mode = i; + return 0; + + case IIO_CHAN_INFO_SAMP_FREQ: + for (i = 0; i < info->num_data_rate_mode_to_hz; i++) { + if (val == info->data_rate_mode_to_hz[i]) + break; + } + if (i == info->num_data_rate_mode_to_hz) + return -EINVAL; + + ads1018->chan_data[chan->scan_index].data_rate_mode = i; + return 0; + + default: + return -EOPNOTSUPP; + } +} + +static int +ads1018_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ads1018_write_raw_direct_mode(indio_dev, chan, val, val2, mask); + iio_device_release_direct(indio_dev); + + return ret; +} + +static int +ads1018_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static const struct iio_info ads1018_iio_info = { + .read_raw = ads1018_read_raw, + .read_avail = ads1018_read_avail, + .write_raw = ads1018_write_raw, + .write_raw_get_fmt = ads1018_write_raw_get_fmt, +}; + +static void ads1018_set_trigger_enable(struct ads1018 *ads1018) +{ + spi_bus_lock(ads1018->spi->controller); + ads1018_spi_read_exclusive(ads1018, NULL, true); + enable_irq(ads1018->drdy_irq); +} + +static void ads1018_set_trigger_disable(struct ads1018 *ads1018) +{ + disable_irq(ads1018->drdy_irq); + ads1018_spi_read_exclusive(ads1018, NULL, false); + spi_bus_unlock(ads1018->spi->controller); +} + +static int ads1018_set_trigger_state(struct iio_trigger *trig, bool state) +{ + struct ads1018 *ads1018 = iio_trigger_get_drvdata(trig); + + /* + * We need to lock the SPI bus and tie CS low (hold_cs) to catch + * data-ready interrupts, otherwise the MISO line enters a Hi-Z state. + */ + + if (state) + ads1018_set_trigger_enable(ads1018); + else + ads1018_set_trigger_disable(ads1018); + + return 0; +} + +static const struct iio_trigger_ops ads1018_trigger_ops = { + .set_trigger_state = ads1018_set_trigger_state, + .validate_device = iio_trigger_validate_own_device, +}; + +static int ads1018_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + const struct ads1018_chip_info *chip_info = ads1018->chip_info; + unsigned int pga, drate, addr; + u16 cfg; + + addr = find_first_bit(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + pga = ads1018->chan_data[addr].pga_mode; + drate = ads1018->chan_data[addr].data_rate_mode; + + cfg = ADS1018_CFG_VALID; + cfg |= FIELD_PREP(ADS1018_CFG_MUX_MASK, addr); + cfg |= FIELD_PREP(ADS1018_CFG_PGA_MASK, pga); + cfg |= FIELD_PREP(ADS1018_CFG_MODE_MASK, ADS1018_MODE_CONTINUOUS); + cfg |= FIELD_PREP(ADS1018_CFG_DRATE_MASK, drate); + + if (chip_info->channels[addr].type == IIO_TEMP) + cfg |= ADS1018_CFG_TS_MODE_EN; + + ads1018->tx_buf[0] = cpu_to_be16(cfg); + ads1018->tx_buf[1] = 0; + + return spi_write(ads1018->spi, ads1018->tx_buf, sizeof(ads1018->tx_buf)); +} + +static int ads1018_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + u16 cfg; + + cfg = ADS1018_CFG_VALID; + cfg |= FIELD_PREP(ADS1018_CFG_MODE_MASK, ADS1018_MODE_ONESHOT); + + ads1018->tx_buf[0] = cpu_to_be16(cfg); + ads1018->tx_buf[1] = 0; + + return spi_write(ads1018->spi, ads1018->tx_buf, sizeof(ads1018->tx_buf)); +} + +static const struct iio_buffer_setup_ops ads1018_buffer_ops = { + .preenable = ads1018_buffer_preenable, + .postdisable = ads1018_buffer_postdisable, + .validate_scan_mask = iio_validate_scan_mask_onehot, +}; + +static irqreturn_t ads1018_irq_handler(int irq, void *dev_id) +{ + struct ads1018 *ads1018 = dev_id; + + /* + * We need to check if the "drdy" pin is actually active or if it's a + * pending interrupt triggered by the SPI transfer. + */ + if (!gpiod_get_value(ads1018->drdy_gpiod)) + return IRQ_HANDLED; + + iio_trigger_poll(ads1018->indio_trig); + + return IRQ_HANDLED; +} + +static irqreturn_t ads1018_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ads1018 *ads1018 = iio_priv(indio_dev); + struct { + __be16 conv; + aligned_s64 ts; + } scan = {}; + int ret; + + if (iio_trigger_using_own(indio_dev)) { + disable_irq(ads1018->drdy_irq); + ret = ads1018_spi_read_exclusive(ads1018, &scan.conv, true); + enable_irq(ads1018->drdy_irq); + } else { + ret = spi_read(ads1018->spi, ads1018->rx_buf, sizeof(ads1018->rx_buf)); + scan.conv = ads1018->rx_buf[0]; + } + + if (!ret) + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ads1018_trigger_setup(struct iio_dev *indio_dev) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + struct spi_device *spi = ads1018->spi; + struct device *dev = &spi->dev; + const char *con_id = "drdy"; + int ret; + + ads1018->drdy_gpiod = devm_gpiod_get_optional(dev, con_id, GPIOD_IN); + if (IS_ERR(ads1018->drdy_gpiod)) + return dev_err_probe(dev, PTR_ERR(ads1018->drdy_gpiod), + "Failed to get %s GPIO.\n", con_id); + + /* First try to get IRQ from SPI core, then from GPIO */ + if (spi->irq > 0) + ads1018->drdy_irq = spi->irq; + else if (ads1018->drdy_gpiod) + ads1018->drdy_irq = gpiod_to_irq(ads1018->drdy_gpiod); + if (ads1018->drdy_irq < 0) + return dev_err_probe(dev, ads1018->drdy_irq, + "Failed to get IRQ from %s GPIO.\n", con_id); + + /* An IRQ line is only an optional requirement for the IIO trigger */ + if (ads1018->drdy_irq == 0) + return 0; + + ads1018->indio_trig = devm_iio_trigger_alloc(dev, "%s-dev%d-%s", + indio_dev->name, + iio_device_id(indio_dev), + con_id); + if (!ads1018->indio_trig) + return -ENOMEM; + + iio_trigger_set_drvdata(ads1018->indio_trig, ads1018); + ads1018->indio_trig->ops = &ads1018_trigger_ops; + + ret = devm_iio_trigger_register(dev, ads1018->indio_trig); + if (ret) + return ret; + + /* + * The "data-ready" IRQ line is shared with the MOSI pin, thus we need + * to keep it disabled until we actually request data. + */ + return devm_request_irq(dev, ads1018->drdy_irq, ads1018_irq_handler, + IRQF_NO_AUTOEN, ads1018->chip_info->name, ads1018); +} + +static int ads1018_spi_probe(struct spi_device *spi) +{ + const struct ads1018_chip_info *info = spi_get_device_match_data(spi); + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ads1018 *ads1018; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*ads1018)); + if (!indio_dev) + return -ENOMEM; + + ads1018 = iio_priv(indio_dev); + ads1018->spi = spi; + ads1018->chip_info = info; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = info->name; + indio_dev->info = &ads1018_iio_info; + indio_dev->channels = info->channels; + indio_dev->num_channels = info->num_channels; + + for (unsigned int i = 0; i < ADS1018_CHANNELS_MAX; i++) { + ads1018->chan_data[i].data_rate_mode = ADS1018_DRATE_DEFAULT; + ads1018->chan_data[i].pga_mode = ADS1018_PGA_DEFAULT; + } + + ads1018->xfer.rx_buf = ads1018->rx_buf; + ads1018->xfer.len = sizeof(ads1018->rx_buf); + spi_message_init_with_transfers(&ads1018->msg_read, &ads1018->xfer, 1); + + ret = ads1018_trigger_setup(indio_dev); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + ads1018_trigger_handler, + &ads1018_buffer_ops); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const unsigned int ads1018_data_rate_table[] = { + 128, 250, 490, 920, 1600, 2400, 3300, +}; + +static const unsigned int ads1118_data_rate_table[] = { + 8, 16, 32, 64, 128, 250, 475, 860, +}; + +static const struct ads1018_chip_info ads1018_chip_info = { + .name = "ads1018", + .channels = ads1018_iio_channels, + .num_channels = ARRAY_SIZE(ads1018_iio_channels), + .data_rate_mode_to_hz = ads1018_data_rate_table, + .num_data_rate_mode_to_hz = ARRAY_SIZE(ads1018_data_rate_table), + .pga_mode_to_gain = { + { 3, 0 }, /* fsr = 6144 mV */ + { 2, 0 }, /* fsr = 4096 mV */ + { 1, 0 }, /* fsr = 2048 mV */ + { 0, 500000000 }, /* fsr = 1024 mV */ + { 0, 250000000 }, /* fsr = 512 mV */ + { 0, 125000000 }, /* fsr = 256 mV */ + }, + .temp_scale = { 125, 0 }, +}; + +static const struct ads1018_chip_info ads1118_chip_info = { + .name = "ads1118", + .channels = ads1118_iio_channels, + .num_channels = ARRAY_SIZE(ads1118_iio_channels), + .data_rate_mode_to_hz = ads1118_data_rate_table, + .num_data_rate_mode_to_hz = ARRAY_SIZE(ads1118_data_rate_table), + .pga_mode_to_gain = { + { 0, 187500000 }, /* fsr = 6144 mV */ + { 0, 125000000 }, /* fsr = 4096 mV */ + { 0, 62500000 }, /* fsr = 2048 mV */ + { 0, 31250000 }, /* fsr = 1024 mV */ + { 0, 15625000 }, /* fsr = 512 mV */ + { 0, 7812500 }, /* fsr = 256 mV */ + }, + .temp_scale = { 31, 250000 }, +}; + +static const struct of_device_id ads1018_of_match[] = { + { .compatible = "ti,ads1018", .data = &ads1018_chip_info }, + { .compatible = "ti,ads1118", .data = &ads1118_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ads1018_of_match); + +static const struct spi_device_id ads1018_spi_match[] = { + { "ads1018", (kernel_ulong_t)&ads1018_chip_info }, + { "ads1118", (kernel_ulong_t)&ads1118_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ads1018_spi_match); + +static struct spi_driver ads1018_spi_driver = { + .driver = { + .name = "ads1018", + .of_match_table = ads1018_of_match, + }, + .probe = ads1018_spi_probe, + .id_table = ads1018_spi_match, +}; +module_spi_driver(ads1018_spi_driver); + +MODULE_DESCRIPTION("Texas Instruments ADS1018 ADC Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kurt Borja "); From 419add567f73dcef9dcc99c67ac8ed89367079b2 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Mon, 15 Dec 2025 11:51:10 -0500 Subject: [PATCH 056/297] dt-bindings: trivial-devices: add MEMSIC 3-axis magnetometer Add compatible string 'memsic,mmc5603' and 'memsic,mmc5633' for MEMSIC 3-axis magnetometer. Acked-by: Conor Dooley Signed-off-by: Frank Li Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/trivial-devices.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index d0f7dbf15d6f..055c9e2b7d47 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -229,6 +229,10 @@ properties: - meas,tsys01 # MEMSIC magnetometer - memsic,mmc35240 + # MEMSIC 3-axis magnetometer + - memsic,mmc5603 + # MEMSIC 3-axis magnetometer (Support I3C HDR) + - memsic,mmc5633 # MEMSIC 3-axis accelerometer - memsic,mxc4005 # MEMSIC 2-axis 8-bit digital accelerometer From 6e5f6bf2e3f036e6d7466d2a3322445729ea3356 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Mon, 15 Dec 2025 11:51:11 -0500 Subject: [PATCH 057/297] iio: magnetometer: Add mmc5633 sensor Add mmc5633 sensor basic support. - Support read 20 bits X/Y/Z magnetic. - Support I3C HDR mode to send start measurememt command. - Support I3C HDR mode to read all sensors data by one command. Co-developed-by: Carlos Song Signed-off-by: Carlos Song Co-developed-by: Adrian Fluturel Signed-off-by: Adrian Fluturel Reviewed-by: Andy Shevchenko Signed-off-by: Frank Li Signed-off-by: Jonathan Cameron --- drivers/iio/magnetometer/Kconfig | 12 + drivers/iio/magnetometer/Makefile | 1 + drivers/iio/magnetometer/mmc5633.c | 586 +++++++++++++++++++++++++++++ 3 files changed, 599 insertions(+) create mode 100644 drivers/iio/magnetometer/mmc5633.c diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig index 81b812a29044..cfb74a4a0836 100644 --- a/drivers/iio/magnetometer/Kconfig +++ b/drivers/iio/magnetometer/Kconfig @@ -139,6 +139,18 @@ config MMC35240 To compile this driver as a module, choose M here: the module will be called mmc35240. +config MMC5633 + tristate "MEMSIC MMC5633 3-axis magnetic sensor" + select REGMAP_I2C + select REGMAP_I3C + depends on I2C || I3C + help + Say yes here to build support for the MEMSIC MMC5633 3-axis + magnetic sensor. + + To compile this driver as a module, choose M here: the module + will be called mmc5633 + config IIO_ST_MAGN_3AXIS tristate "STMicroelectronics magnetometers 3-Axis Driver" depends on (I2C || SPI_MASTER) && SYSFS diff --git a/drivers/iio/magnetometer/Makefile b/drivers/iio/magnetometer/Makefile index dfe970fcacb8..5bd227f8c120 100644 --- a/drivers/iio/magnetometer/Makefile +++ b/drivers/iio/magnetometer/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_BMC150_MAGN_SPI) += bmc150_magn_spi.o obj-$(CONFIG_MAG3110) += mag3110.o obj-$(CONFIG_HID_SENSOR_MAGNETOMETER_3D) += hid-sensor-magn-3d.o obj-$(CONFIG_MMC35240) += mmc35240.o +obj-$(CONFIG_MMC5633) += mmc5633.o obj-$(CONFIG_IIO_ST_MAGN_3AXIS) += st_magn.o st_magn-y := st_magn_core.o diff --git a/drivers/iio/magnetometer/mmc5633.c b/drivers/iio/magnetometer/mmc5633.c new file mode 100644 index 000000000000..9d8e27486963 --- /dev/null +++ b/drivers/iio/magnetometer/mmc5633.c @@ -0,0 +1,586 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MMC5633 - MEMSIC 3-axis Magnetic Sensor + * + * Copyright (c) 2015, Intel Corporation. + * Copyright (c) 2025, NXP + * + * IIO driver for MMC5633, base on mmc35240.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MMC5633_REG_XOUT0 0x00 +#define MMC5633_REG_XOUT1 0x01 +#define MMC5633_REG_YOUT0 0x02 +#define MMC5633_REG_YOUT1 0x03 +#define MMC5633_REG_ZOUT0 0x04 +#define MMC5633_REG_ZOUT1 0x05 +#define MMC5633_REG_XOUT2 0x06 +#define MMC5633_REG_YOUT2 0x07 +#define MMC5633_REG_ZOUT2 0x08 +#define MMC5633_REG_TOUT 0x09 + +#define MMC5633_REG_STATUS1 0x18 +#define MMC5633_REG_STATUS0 0x19 +#define MMC5633_REG_CTRL0 0x1b +#define MMC5633_REG_CTRL1 0x1c +#define MMC5633_REG_CTRL2 0x1d + +#define MMC5633_REG_ID 0x39 + +#define MMC5633_STATUS1_MEAS_T_DONE_BIT BIT(7) +#define MMC5633_STATUS1_MEAS_M_DONE_BIT BIT(6) + +#define MMC5633_CTRL0_CMM_FREQ_EN BIT(7) +#define MMC5633_CTRL0_AUTO_ST_EN BIT(6) +#define MMC5633_CTRL0_AUTO_SR_EN BIT(5) +#define MMC5633_CTRL0_RESET BIT(4) +#define MMC5633_CTRL0_SET BIT(3) +#define MMC5633_CTRL0_MEAS_T BIT(1) +#define MMC5633_CTRL0_MEAS_M BIT(0) + +#define MMC5633_CTRL1_BW_MASK GENMASK(1, 0) + +#define MMC5633_WAIT_SET_RESET_US (1 * USEC_PER_MSEC) + +#define MMC5633_HDR_CTRL0_MEAS_M 0x01 +#define MMC5633_HDR_CTRL0_MEAS_T 0x03 +#define MMC5633_HDR_CTRL0_SET 0x05 +#define MMC5633_HDR_CTRL0_RESET 0x07 + +enum mmc5633_axis { + MMC5633_AXIS_X, + MMC5633_AXIS_Y, + MMC5633_AXIS_Z, + MMC5633_TEMPERATURE, +}; + +struct mmc5633_data { + struct regmap *regmap; + struct i3c_device *i3cdev; + struct mutex mutex; /* protect to finish one whole measurement */ +}; + +static int mmc5633_samp_freq[][2] = { + { 1, 200000 }, + { 2, 0 }, + { 3, 500000 }, + { 6, 600000 }, +}; + +#define MMC5633_CHANNEL(_axis) { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## _axis, \ + .address = MMC5633_AXIS_ ## _axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec mmc5633_channels[] = { + MMC5633_CHANNEL(X), + MMC5633_CHANNEL(Y), + MMC5633_CHANNEL(Z), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = MMC5633_TEMPERATURE, + }, +}; + +static int mmc5633_get_samp_freq_index(struct mmc5633_data *data, + int val, int val2) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(mmc5633_samp_freq); i++) + if (mmc5633_samp_freq[i][0] == val && + mmc5633_samp_freq[i][1] == val2) + return i; + return -EINVAL; +} + +static int mmc5633_init(struct mmc5633_data *data) +{ + unsigned int reg_id; + int ret; + + ret = regmap_read(data->regmap, MMC5633_REG_ID, ®_id); + if (ret) + return dev_err_probe(regmap_get_device(data->regmap), ret, + "Error reading product id\n"); + + /* + * Make sure we restore sensor characteristics, by doing + * a SET/RESET sequence, the axis polarity being naturally + * aligned after RESET. + */ + ret = regmap_write(data->regmap, MMC5633_REG_CTRL0, MMC5633_CTRL0_SET); + if (ret) + return ret; + + /* + * Minimum time interval between SET or RESET to other operations is + * 1ms according to Operating Timing Diagram in datasheet. + */ + fsleep(MMC5633_WAIT_SET_RESET_US); + + ret = regmap_write(data->regmap, MMC5633_REG_CTRL0, MMC5633_CTRL0_RESET); + if (ret) + return ret; + + /* set default sampling frequency */ + return regmap_update_bits(data->regmap, MMC5633_REG_CTRL1, + MMC5633_CTRL1_BW_MASK, + FIELD_PREP(MMC5633_CTRL1_BW_MASK, 0)); +} + +static int mmc5633_take_measurement(struct mmc5633_data *data, int address) +{ + unsigned int reg_status, val; + int ret; + + val = (address == MMC5633_TEMPERATURE) ? MMC5633_CTRL0_MEAS_T : MMC5633_CTRL0_MEAS_M; + ret = regmap_write(data->regmap, MMC5633_REG_CTRL0, val); + if (ret < 0) + return ret; + + val = (address == MMC5633_TEMPERATURE) ? + MMC5633_STATUS1_MEAS_T_DONE_BIT : MMC5633_STATUS1_MEAS_M_DONE_BIT; + ret = regmap_read_poll_timeout(data->regmap, MMC5633_REG_STATUS1, reg_status, + reg_status & val, + 10 * USEC_PER_MSEC, + 100 * 10 * USEC_PER_MSEC); + if (ret) { + dev_err(regmap_get_device(data->regmap), "data not ready\n"); + return ret; + } + + return 0; +} + +static bool mmc5633_is_support_hdr(struct mmc5633_data *data) +{ + if (!data->i3cdev) + return false; + + return i3c_device_get_supported_xfer_mode(data->i3cdev) & BIT(I3C_HDR_DDR); +} + +static int mmc5633_read_measurement(struct mmc5633_data *data, int address, void *buf, size_t sz) +{ + struct device *dev = regmap_get_device(data->regmap); + u8 data_cmd[2], status[2]; + unsigned int val, ready; + int ret; + + if (mmc5633_is_support_hdr(data)) { + struct i3c_xfer xfers_wr_cmd[] = { + { + .cmd = 0x3b, + .len = 2, + .data.out = data_cmd, + } + }; + struct i3c_xfer xfers_rd_sta_cmd[] = { + { + .cmd = 0x23 | BIT(7), /* RDSTA CMD */ + .len = 2, + .data.in = status, + }, + }; + struct i3c_xfer xfers_rd_data_cmd[] = { + { + .cmd = 0x22 | BIT(7), /* RDLONG CMD */ + .len = sz, + .data.in = buf, + }, + }; + + data_cmd[0] = 0; + data_cmd[1] = (address == MMC5633_TEMPERATURE) ? + MMC5633_HDR_CTRL0_MEAS_T : MMC5633_HDR_CTRL0_MEAS_M; + + ret = i3c_device_do_xfers(data->i3cdev, xfers_wr_cmd, + ARRAY_SIZE(xfers_wr_cmd), I3C_HDR_DDR); + if (ret < 0) + return ret; + + ready = (address == MMC5633_TEMPERATURE) ? + MMC5633_STATUS1_MEAS_T_DONE_BIT : MMC5633_STATUS1_MEAS_M_DONE_BIT; + ret = read_poll_timeout(i3c_device_do_xfers, val, + val || (status[0] & ready), + 10 * USEC_PER_MSEC, + 100 * 10 * USEC_PER_MSEC, 0, + data->i3cdev, xfers_rd_sta_cmd, + ARRAY_SIZE(xfers_rd_sta_cmd), I3C_HDR_DDR); + if (ret) { + dev_err(dev, "data not ready\n"); + return ret; + } + if (val) { + dev_err(dev, "i3c transfer error\n"); + return val; + } + return i3c_device_do_xfers(data->i3cdev, xfers_rd_data_cmd, + ARRAY_SIZE(xfers_rd_data_cmd), I3C_HDR_DDR); + } + + /* Fallback to use SDR/I2C mode */ + ret = mmc5633_take_measurement(data, address); + if (ret < 0) + return ret; + + if (address == MMC5633_TEMPERATURE) + /* + * Put tempeature to last byte of buff to align HDR case. + * I3C will early terminate data read if previous data is not + * available. + */ + return regmap_bulk_read(data->regmap, MMC5633_REG_TOUT, buf + sz - 1, 1); + + return regmap_bulk_read(data->regmap, MMC5633_REG_XOUT0, buf, sz); +} + +/* X,Y,Z 3 channels, each channel has 3 byte and TEMP */ +#define MMC5633_ALL_SIZE (3 * 3 + 1) + +static int mmc5633_get_raw(struct mmc5633_data *data, int index, unsigned char *buf, int *val) +{ + if (index == MMC5633_TEMPERATURE) { + *val = buf[MMC5633_ALL_SIZE - 1]; + return 0; + } + /* + * X[19..12] X[11..4] Y[19..12] Y[11..4] Z[19..12] Z[11..4] X[3..0] Y[3..0] Z[3..0] + */ + *val = get_unaligned_be16(buf + 2 * index) << 4; + *val |= buf[index + 6] >> 4; + + return 0; +} + +static int mmc5633_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct mmc5633_data *data = iio_priv(indio_dev); + char buf[MMC5633_ALL_SIZE]; + unsigned int reg, i; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + scoped_guard(mutex, &data->mutex) { + ret = mmc5633_read_measurement(data, chan->address, buf, MMC5633_ALL_SIZE); + if (ret < 0) + return ret; + } + + ret = mmc5633_get_raw(data, chan->address, buf, val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_MAGN) { + *val = 0; + *val2 = 62500; + } else { + *val = 0; + *val2 = 800000000; /* 0.8C */ + } + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + if (chan->type == IIO_TEMP) { + *val = -75; + return IIO_VAL_INT; + } + return -EINVAL; + case IIO_CHAN_INFO_SAMP_FREQ: + scoped_guard(mutex, &data->mutex) { + ret = regmap_read(data->regmap, MMC5633_REG_CTRL1, ®); + if (ret < 0) + return ret; + } + + i = FIELD_GET(MMC5633_CTRL1_BW_MASK, reg); + if (i >= ARRAY_SIZE(mmc5633_samp_freq)) + return -EINVAL; + + *val = mmc5633_samp_freq[i][0]; + *val2 = mmc5633_samp_freq[i][1]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int mmc5633_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct mmc5633_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: { + ret = mmc5633_get_samp_freq_index(data, val, val2); + if (ret < 0) + return ret; + + guard(mutex)(&data->mutex); + + return regmap_update_bits(data->regmap, MMC5633_REG_CTRL1, + MMC5633_CTRL1_BW_MASK, + FIELD_PREP(MMC5633_CTRL1_BW_MASK, ret)); + } + default: + return -EINVAL; + } +} + +static int mmc5633_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (const int *)mmc5633_samp_freq; + *length = ARRAY_SIZE(mmc5633_samp_freq) * 2; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_info mmc5633_info = { + .read_raw = mmc5633_read_raw, + .write_raw = mmc5633_write_raw, + .read_avail = mmc5633_read_avail, +}; + +static bool mmc5633_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC5633_REG_CTRL0: + case MMC5633_REG_CTRL1: + return true; + default: + return false; + } +} + +static bool mmc5633_is_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC5633_REG_XOUT0: + case MMC5633_REG_XOUT1: + case MMC5633_REG_YOUT0: + case MMC5633_REG_YOUT1: + case MMC5633_REG_ZOUT0: + case MMC5633_REG_ZOUT1: + case MMC5633_REG_XOUT2: + case MMC5633_REG_YOUT2: + case MMC5633_REG_ZOUT2: + case MMC5633_REG_TOUT: + case MMC5633_REG_STATUS1: + case MMC5633_REG_ID: + return true; + default: + return false; + } +} + +static bool mmc5633_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC5633_REG_CTRL0: + case MMC5633_REG_CTRL1: + return false; + default: + return true; + } +} + +static const struct reg_default mmc5633_reg_defaults[] = { + { MMC5633_REG_CTRL0, 0x00 }, + { MMC5633_REG_CTRL1, 0x00 }, +}; + +static const struct regmap_config mmc5633_regmap_config = { + .name = "mmc5633_regmap", + + .reg_bits = 8, + .val_bits = 8, + + .max_register = MMC5633_REG_ID, + .cache_type = REGCACHE_MAPLE, + + .writeable_reg = mmc5633_is_writeable_reg, + .readable_reg = mmc5633_is_readable_reg, + .volatile_reg = mmc5633_is_volatile_reg, + + .reg_defaults = mmc5633_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(mmc5633_reg_defaults), +}; + +static int mmc5633_common_probe(struct regmap *regmap, char *name, + struct i3c_device *i3cdev) +{ + struct device *dev = regmap_get_device(regmap); + struct mmc5633_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + + data->regmap = regmap; + data->i3cdev = i3cdev; + + ret = devm_mutex_init(dev, &data->mutex); + if (ret) + return ret; + + indio_dev->info = &mmc5633_info; + indio_dev->name = name; + indio_dev->channels = mmc5633_channels; + indio_dev->num_channels = ARRAY_SIZE(mmc5633_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = mmc5633_init(data); + if (ret < 0) + return dev_err_probe(dev, ret, "mmc5633 chip init failed\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static int mmc5633_suspend(struct device *dev) +{ + struct regmap *regmap = dev_get_regmap(dev, NULL); + + regcache_cache_only(regmap, true); + + return 0; +} + +static int mmc5633_resume(struct device *dev) +{ + struct regmap *regmap = dev_get_regmap(dev, NULL); + int ret; + + regcache_mark_dirty(regmap); + ret = regcache_sync_region(regmap, MMC5633_REG_CTRL0, MMC5633_REG_CTRL1); + if (ret) + dev_err(dev, "Failed to restore control registers\n"); + + regcache_cache_only(regmap, false); + + return 0; +} + +static int mmc5633_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &mmc5633_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "regmap init failed\n"); + + return mmc5633_common_probe(regmap, client->name, NULL); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mmc5633_pm_ops, mmc5633_suspend, mmc5633_resume); + +static const struct of_device_id mmc5633_of_match[] = { + { .compatible = "memsic,mmc5603" }, + { .compatible = "memsic,mmc5633" }, + { } +}; +MODULE_DEVICE_TABLE(of, mmc5633_of_match); + +static const struct i2c_device_id mmc5633_i2c_id[] = { + { "mmc5603" }, + { "mmc5633" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mmc5633_i2c_id); + +static struct i2c_driver mmc5633_i2c_driver = { + .driver = { + .name = "mmc5633_i2c", + .of_match_table = mmc5633_of_match, + .pm = pm_sleep_ptr(&mmc5633_pm_ops), + }, + .probe = mmc5633_i2c_probe, + .id_table = mmc5633_i2c_id, +}; + +static const struct i3c_device_id mmc5633_i3c_ids[] = { + I3C_DEVICE(0x0251, 0x0000, NULL), + { } +}; +MODULE_DEVICE_TABLE(i3c, mmc5633_i3c_ids); + +static int mmc5633_i3c_probe(struct i3c_device *i3cdev) +{ + struct device *dev = i3cdev_to_dev(i3cdev); + struct regmap *regmap; + char *name; + + name = devm_kasprintf(dev, GFP_KERNEL, "mmc5633_%s", dev_name(dev)); + if (!name) + return -ENOMEM; + + regmap = devm_regmap_init_i3c(i3cdev, &mmc5633_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to register i3c regmap\n"); + + return mmc5633_common_probe(regmap, name, i3cdev); +} + +static struct i3c_driver mmc5633_i3c_driver = { + .driver = { + .name = "mmc5633_i3c", + }, + .probe = mmc5633_i3c_probe, + .id_table = mmc5633_i3c_ids, +}; +module_i3c_i2c_driver(mmc5633_i3c_driver, &mmc5633_i2c_driver) + +MODULE_AUTHOR("Frank Li "); +MODULE_DESCRIPTION("MEMSIC MMC5633 magnetic sensor driver"); +MODULE_LICENSE("GPL"); From eec44b04eb0e93143f89a2e2cb04159f62c2e8a3 Mon Sep 17 00:00:00 2001 From: Tomas Borquez Date: Mon, 15 Dec 2025 16:08:02 -0300 Subject: [PATCH 058/297] staging: iio: ad9832: clean up whitespace Remove unnecessary blank lines between comment sections to improve readability. Signed-off-by: Tomas Borquez Signed-off-by: Jonathan Cameron --- drivers/staging/iio/frequency/ad9832.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/staging/iio/frequency/ad9832.c b/drivers/staging/iio/frequency/ad9832.c index e2ad3e5a7a8e..b87ea1781b27 100644 --- a/drivers/staging/iio/frequency/ad9832.c +++ b/drivers/staging/iio/frequency/ad9832.c @@ -26,7 +26,6 @@ #include "dds.h" /* Registers */ - #define AD9832_FREQ0LL 0x0 #define AD9832_FREQ0HL 0x1 #define AD9832_FREQ0LM 0x2 @@ -43,14 +42,12 @@ #define AD9832_PHASE2H 0xD #define AD9832_PHASE3L 0xE #define AD9832_PHASE3H 0xF - #define AD9832_PHASE_SYM 0x10 #define AD9832_FREQ_SYM 0x11 #define AD9832_PINCTRL_EN 0x12 #define AD9832_OUTPUT_EN 0x13 /* Command Control Bits */ - #define AD9832_CMD_PHA8BITSW 0x1 #define AD9832_CMD_PHA16BITSW 0x0 #define AD9832_CMD_FRE8BITSW 0x3 @@ -90,7 +87,6 @@ * @phase_data: tuning word spi transmit buffer * @freq_data: tuning word spi transmit buffer */ - struct ad9832_state { struct spi_device *spi; struct clk *mclk; @@ -327,7 +323,6 @@ static int ad9832_probe(struct spi_device *spi) indio_dev->modes = INDIO_DIRECT_MODE; /* Setup default messages */ - st->xfer.tx_buf = &st->data; st->xfer.len = 2; From 4e44c635ba8c281f74001d47e20bbcb7f516530c Mon Sep 17 00:00:00 2001 From: "Yury Norov (NVIDIA)" Date: Mon, 15 Dec 2025 19:18:06 -0500 Subject: [PATCH 059/297] iio: adc: ad7606_spi: use bitmap_full() in ad7606_spi_update_scan_mode() bitmap_full() is less verbose and more efficient, as it stops traversing scan_mask as soon as the 1st unset bit found. Signed-off-by: Yury Norov (NVIDIA) Reviewed-by: David Lechner Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7606_spi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/adc/ad7606_spi.c b/drivers/iio/adc/ad7606_spi.c index f28e4ca37707..7e17ccbcedd0 100644 --- a/drivers/iio/adc/ad7606_spi.c +++ b/drivers/iio/adc/ad7606_spi.c @@ -345,7 +345,7 @@ static int ad7606_spi_update_scan_mode(struct iio_dev *indio_dev, * has no way of demuxing the data to filter out unwanted * channels. */ - if (bitmap_weight(scan_mask, num_adc_ch) != num_adc_ch) + if (!bitmap_full(scan_mask, num_adc_ch)) return -EINVAL; } From ad415677b7e3b733270adaf04e3a7a9c46f1e929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Date: Tue, 14 Oct 2025 17:59:05 +0300 Subject: [PATCH 060/297] MAINTAINERS: Update Intel Quadrature Encoder Peripheral maintainer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Jarkko's address is going to bounce soon and I agreed to be the new maintainer. Acked-by: Jarkko Nikula Signed-off-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20251014145905.4862-1-ilpo.jarvinen@linux.intel.com Signed-off-by: William Breathitt Gray --- MAINTAINERS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index dc731d37c8fe..9f61a449fca7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12978,8 +12978,9 @@ S: Orphan F: drivers/ptp/ptp_dfl_tod.c INTEL QUADRATURE ENCODER PERIPHERAL DRIVER -M: Jarkko Nikula +M: Ilpo Järvinen L: linux-iio@vger.kernel.org +S: Supported F: drivers/counter/intel-qep.c INTEL SCU DRIVERS From 10d4dbdc8fbce586b17be07b8138e025381453dd Mon Sep 17 00:00:00 2001 From: James Clark Date: Fri, 28 Nov 2025 11:55:13 +0000 Subject: [PATCH 061/297] coresight: Change syncfreq to be a u8 TRCSYNCPR.PERIOD is the only functional part of TRCSYNCPR and it only has 5 valid bits so it can be stored in a u8. Reviewed-by: Mike Leach Reviewed-by: Leo Yan Tested-by: Leo Yan Signed-off-by: James Clark Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251128-james-cs-syncfreq-v8-1-4d319764cc58@linaro.org --- drivers/hwtracing/coresight/coresight-etm4x.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwtracing/coresight/coresight-etm4x.h b/drivers/hwtracing/coresight/coresight-etm4x.h index 012c52fd1933..0287d19ce12e 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x.h +++ b/drivers/hwtracing/coresight/coresight-etm4x.h @@ -825,7 +825,6 @@ struct etmv4_config { u32 eventctrl1; u32 stall_ctrl; u32 ts_ctrl; - u32 syncfreq; u32 ccctlr; u32 bb_ctrl; u32 vinst_ctrl; @@ -833,6 +832,7 @@ struct etmv4_config { u32 vissctlr; u32 vipcssctlr; u8 seq_idx; + u8 syncfreq; u32 seq_ctrl[ETM_MAX_SEQ_STATES]; u32 seq_rst; u32 seq_state; From 38f4c42734990ddf42ad6d996b14fbff8fdec9d2 Mon Sep 17 00:00:00 2001 From: James Clark Date: Fri, 28 Nov 2025 11:55:14 +0000 Subject: [PATCH 062/297] coresight: Repack struct etmv4_drvdata Fix holes and convert the long list of bools to single bits to save some space because there's one of these for each ETM. Reviewed-by: Leo Yan Reviewed-by: Mike Leach Tested-by: Leo Yan Signed-off-by: James Clark Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251128-james-cs-syncfreq-v8-2-4d319764cc58@linaro.org --- drivers/hwtracing/coresight/coresight-etm4x.h | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm4x.h b/drivers/hwtracing/coresight/coresight-etm4x.h index 0287d19ce12e..d178d79d9827 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x.h +++ b/drivers/hwtracing/coresight/coresight-etm4x.h @@ -1016,27 +1016,27 @@ struct etmv4_drvdata { u8 ns_ex_level; u8 q_support; u8 os_lock_model; - bool sticky_enable; - bool boot_enable; - bool os_unlock; - bool instrp0; - bool q_filt; - bool trcbb; - bool trccond; - bool retstack; - bool trccci; - bool trc_error; - bool syncpr; - bool stallctl; - bool sysstall; - bool nooverflow; - bool atbtrig; - bool lpoverride; + bool sticky_enable : 1; + bool boot_enable : 1; + bool os_unlock : 1; + bool instrp0 : 1; + bool q_filt : 1; + bool trcbb : 1; + bool trccond : 1; + bool retstack : 1; + bool trccci : 1; + bool trc_error : 1; + bool syncpr : 1; + bool stallctl : 1; + bool sysstall : 1; + bool nooverflow : 1; + bool atbtrig : 1; + bool lpoverride : 1; + bool skip_power_up : 1; + bool paused : 1; u64 trfcr; struct etmv4_config config; struct etmv4_save_state *save_state; - bool skip_power_up; - bool paused; DECLARE_BITMAP(arch_features, ETM4_IMPDEF_FEATURE_MAX); }; From b02450de6ba6309c66e2e056ccfbfce4bd3b0352 Mon Sep 17 00:00:00 2001 From: James Clark Date: Fri, 28 Nov 2025 11:55:15 +0000 Subject: [PATCH 063/297] coresight: Refactor etm4_config_timestamp_event() Remove some of the magic numbers and try to clarify some of the documentation so it's clearer how this sets up the timestamp interval. Return errors directly instead of jumping to out and returning ret, nothing needs to be cleaned up at the end and it only obscures the flow and return value. Add utilities for programming resource selectors that do compile time checks for constants or WARN_ONs for non-constant values. FIELD_PREP includes compile time checks so we only need to add an additional BUILD_BUG_ON for resource == 0 in pair mode. Tested-by: Leo Yan Signed-off-by: James Clark Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251128-james-cs-syncfreq-v8-3-4d319764cc58@linaro.org --- .../coresight/coresight-etm4x-core.c | 96 ++++++++++++------- drivers/hwtracing/coresight/coresight-etm4x.h | 54 ++++++++++- 2 files changed, 112 insertions(+), 38 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c index 560975b70474..2ec2ae1fef58 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c @@ -642,18 +642,33 @@ static void etm4_enable_sysfs_smp_call(void *info) * TRCRSCTLR1 (always true) used to get the counter to decrement. From * there a resource selector is configured with the counter and the * timestamp control register to use the resource selector to trigger the - * event that will insert a timestamp packet in the stream. + * event that will insert a timestamp packet in the stream: + * + * +--------------+ + * | Resource 1 | fixed "always-true" resource + * +--------------+ + * | + * +------v-------+ + * | Counter x | (reload to 1 on underflow) + * +--------------+ + * | + * +------v--------------+ + * | Resource Selector y | (trigger on counter x == 0) + * +---------------------+ + * | + * +------v---------------+ + * | Timestamp Generator | (timestamp on resource y) + * +----------------------+ */ static int etm4_config_timestamp_event(struct etmv4_drvdata *drvdata) { - int ctridx, ret = -EINVAL; - int counter, rselector; - u32 val = 0; + int ctridx; + int rselector; struct etmv4_config *config = &drvdata->config; /* No point in trying if we don't have at least one counter */ if (!drvdata->nr_cntr) - goto out; + return -EINVAL; /* Find a counter that hasn't been initialised */ for (ctridx = 0; ctridx < drvdata->nr_cntr; ctridx++) @@ -663,15 +678,19 @@ static int etm4_config_timestamp_event(struct etmv4_drvdata *drvdata) /* All the counters have been configured already, bail out */ if (ctridx == drvdata->nr_cntr) { pr_debug("%s: no available counter found\n", __func__); - ret = -ENOSPC; - goto out; + return -ENOSPC; } /* - * Searching for an available resource selector to use, starting at - * '2' since every implementation has at least 2 resource selector. - * ETMIDR4 gives the number of resource selector _pairs_, - * hence multiply by 2. + * Searching for an available resource selector to use, starting at '2' + * since resource 0 is the fixed 'always returns false' resource and 1 + * is the fixed 'always returns true' resource. See IHI0064H_b '7.3.64 + * TRCRSCTLRn, Resource Selection Control Registers, n=2-31'. If there + * are no resources, there would also be no counters so wouldn't get + * here. + * + * ETMIDR4 gives the number of resource selector _pairs_, hence multiply + * by 2. */ for (rselector = 2; rselector < drvdata->nr_resource * 2; rselector++) if (!config->res_ctrl[rselector]) @@ -680,13 +699,9 @@ static int etm4_config_timestamp_event(struct etmv4_drvdata *drvdata) if (rselector == drvdata->nr_resource * 2) { pr_debug("%s: no available resource selector found\n", __func__); - ret = -ENOSPC; - goto out; + return -ENOSPC; } - /* Remember what counter we used */ - counter = 1 << ctridx; - /* * Initialise original and reload counter value to the smallest * possible value in order to get as much precision as we can. @@ -694,26 +709,41 @@ static int etm4_config_timestamp_event(struct etmv4_drvdata *drvdata) config->cntr_val[ctridx] = 1; config->cntrldvr[ctridx] = 1; - /* Set the trace counter control register */ - val = 0x1 << 16 | /* Bit 16, reload counter automatically */ - 0x0 << 7 | /* Select single resource selector */ - 0x1; /* Resource selector 1, i.e always true */ + /* + * Trace Counter Control Register TRCCNTCTLRn + * + * CNTCHAIN = 0, don't reload on the previous counter + * RLDSELF = true, reload counter automatically on underflow + * RLDEVENT = RES_SEL_FALSE (0), reload on single false resource (never reload) + * CNTEVENT = RES_SEL_TRUE (1), count single fixed 'always true' resource (always decrement) + */ + config->cntr_ctrl[ctridx] = TRCCNTCTLRn_RLDSELF | + FIELD_PREP(TRCCNTCTLRn_RLDEVENT_MASK, + etm4_res_sel_single(ETM4_RES_SEL_FALSE)) | + FIELD_PREP(TRCCNTCTLRn_CNTEVENT_MASK, + etm4_res_sel_single(ETM4_RES_SEL_TRUE)); - config->cntr_ctrl[ctridx] = val; + /* + * Resource Selection Control Register TRCRSCTLRn + * + * PAIRINV = 0, INV = 0, don't invert + * GROUP = 2, SELECT = ctridx, trigger when counter 'ctridx' reaches 0 + * + * Multiple counters can be selected, and each bit signifies a counter, + * so set bit 'ctridx' to select our counter. + */ + config->res_ctrl[rselector] = FIELD_PREP(TRCRSCTLRn_GROUP_MASK, 2) | + FIELD_PREP(TRCRSCTLRn_SELECT_MASK, 1 << ctridx); - val = 0x2 << 16 | /* Group 0b0010 - Counter and sequencers */ - counter << 0; /* Counter to use */ + /* + * Global Timestamp Control Register TRCTSCTLR + * + * EVENT = generate timestamp on single resource 'rselector' + */ + config->ts_ctrl = FIELD_PREP(TRCTSCTLR_EVENT_MASK, + etm4_res_sel_single(rselector)); - config->res_ctrl[rselector] = val; - - val = 0x0 << 7 | /* Select single resource selector */ - rselector; /* Resource selector */ - - config->ts_ctrl = val; - - ret = 0; -out: - return ret; + return 0; } static int etm4_parse_event_config(struct coresight_device *csdev, diff --git a/drivers/hwtracing/coresight/coresight-etm4x.h b/drivers/hwtracing/coresight/coresight-etm4x.h index d178d79d9827..89d81ce4e04e 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x.h +++ b/drivers/hwtracing/coresight/coresight-etm4x.h @@ -225,6 +225,50 @@ #define TRCRSCTLRn_GROUP_MASK GENMASK(19, 16) #define TRCRSCTLRn_SELECT_MASK GENMASK(15, 0) +#define TRCCNTCTLRn_CNTCHAIN BIT(17) +#define TRCCNTCTLRn_RLDSELF BIT(16) +#define TRCCNTCTLRn_RLDEVENT_MASK GENMASK(15, 8) +#define TRCCNTCTLRn_CNTEVENT_MASK GENMASK(7, 0) + +#define TRCTSCTLR_EVENT_MASK GENMASK(7, 0) + +#define ETM4_RES_SEL_FALSE 0 /* Fixed function 'always false' resource selector */ +#define ETM4_RES_SEL_TRUE 1 /* Fixed function 'always true' resource selector */ + +#define ETM4_RES_SEL_SINGLE_MASK GENMASK(4, 0) +#define ETM4_RES_SEL_PAIR_MASK GENMASK(3, 0) +#define ETM4_RES_SEL_TYPE_PAIR BIT(7) + +/* + * Utilities for programming EVENT resource selectors, e.g. TRCCNTCTLRn_RLDEVENT. + * + * Resource selectors have a common format across registers: + * + * 7 6 5 4 0 + * +------+------+-------+ + * | TYPE | RES0 | SEL | + * +------+------+-------+ + * + * Where TYPE indicates whether the selector is for a single event or a pair. + * When TYPE is pair, SEL is 4 bits wide and using pair 0 is UNPREDICTABLE. + * Otherwise for single it's 5 bits wide. + */ +static inline u32 etm4_res_sel_single(u8 res_sel_idx) +{ + WARN_ON_ONCE(!FIELD_FIT(ETM4_RES_SEL_SINGLE_MASK, res_sel_idx)); + return FIELD_PREP(ETM4_RES_SEL_SINGLE_MASK, res_sel_idx); +} + +static inline u32 etm4_res_sel_pair(u8 res_sel_idx) +{ + if (__builtin_constant_p(res_sel_idx)) + BUILD_BUG_ON(res_sel_idx == 0); + WARN_ON_ONCE(!FIELD_FIT(ETM4_RES_SEL_PAIR_MASK, res_sel_idx) || + (res_sel_idx == 0)); + return FIELD_PREP(ETM4_RES_SEL_PAIR_MASK, res_sel_idx) | + ETM4_RES_SEL_TYPE_PAIR; +} + /* * System instructions to access ETM registers. * See ETMv4.4 spec ARM IHI0064F section 4.3.6 System instructions @@ -824,7 +868,7 @@ struct etmv4_config { u32 eventctrl0; u32 eventctrl1; u32 stall_ctrl; - u32 ts_ctrl; + u32 ts_ctrl; /* TRCTSCTLR */ u32 ccctlr; u32 bb_ctrl; u32 vinst_ctrl; @@ -837,11 +881,11 @@ struct etmv4_config { u32 seq_rst; u32 seq_state; u8 cntr_idx; - u32 cntrldvr[ETMv4_MAX_CNTR]; - u32 cntr_ctrl[ETMv4_MAX_CNTR]; - u32 cntr_val[ETMv4_MAX_CNTR]; + u32 cntrldvr[ETMv4_MAX_CNTR]; /* TRCCNTRLDVRn */ + u32 cntr_ctrl[ETMv4_MAX_CNTR]; /* TRCCNTCTLRn */ + u32 cntr_val[ETMv4_MAX_CNTR]; /* TRCCNTVRn */ u8 res_idx; - u32 res_ctrl[ETM_MAX_RES_SEL]; + u32 res_ctrl[ETM_MAX_RES_SEL]; /* TRCRSCTLRn */ u8 ss_idx; u32 ss_ctrl[ETM_MAX_SS_CMP]; u32 ss_status[ETM_MAX_SS_CMP]; From 20bc2ea23774a7655d21ce2243eccfdfd4405279 Mon Sep 17 00:00:00 2001 From: James Clark Date: Fri, 28 Nov 2025 11:55:16 +0000 Subject: [PATCH 064/297] coresight: Hide unused ETMv3 format attributes ETMv3 only has a few attributes, and setting unused ones results in an error, so hide them to begin with. Reviewed-by: Mike Leach Tested-by: Leo Yan Signed-off-by: James Clark Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251128-james-cs-syncfreq-v8-4-4d319764cc58@linaro.org --- .../hwtracing/coresight/coresight-etm-perf.c | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm-perf.c b/drivers/hwtracing/coresight/coresight-etm-perf.c index 17afa0f4cdee..3805282b97e8 100644 --- a/drivers/hwtracing/coresight/coresight-etm-perf.c +++ b/drivers/hwtracing/coresight/coresight-etm-perf.c @@ -50,27 +50,23 @@ struct etm_ctxt { static DEFINE_PER_CPU(struct etm_ctxt, etm_ctxt); static DEFINE_PER_CPU(struct coresight_device *, csdev_src); -/* - * The PMU formats were orignally for ETMv3.5/PTM's ETMCR 'config'; - * now take them as general formats and apply on all ETMs. - */ -PMU_FORMAT_ATTR(branch_broadcast, "config:"__stringify(ETM_OPT_BRANCH_BROADCAST)); PMU_FORMAT_ATTR(cycacc, "config:" __stringify(ETM_OPT_CYCACC)); -/* contextid1 enables tracing CONTEXTIDR_EL1 for ETMv4 */ -PMU_FORMAT_ATTR(contextid1, "config:" __stringify(ETM_OPT_CTXTID)); -/* contextid2 enables tracing CONTEXTIDR_EL2 for ETMv4 */ -PMU_FORMAT_ATTR(contextid2, "config:" __stringify(ETM_OPT_CTXTID2)); PMU_FORMAT_ATTR(timestamp, "config:" __stringify(ETM_OPT_TS)); PMU_FORMAT_ATTR(retstack, "config:" __stringify(ETM_OPT_RETSTK)); +PMU_FORMAT_ATTR(sinkid, "config2:0-31"); + +#if IS_ENABLED(CONFIG_CORESIGHT_SOURCE_ETM4X) +PMU_FORMAT_ATTR(branch_broadcast, "config:"__stringify(ETM_OPT_BRANCH_BROADCAST)); +/* contextid1 enables tracing CONTEXTIDR_EL1*/ +PMU_FORMAT_ATTR(contextid1, "config:" __stringify(ETM_OPT_CTXTID)); +/* contextid2 enables tracing CONTEXTIDR_EL2*/ +PMU_FORMAT_ATTR(contextid2, "config:" __stringify(ETM_OPT_CTXTID2)); /* preset - if sink ID is used as a configuration selector */ PMU_FORMAT_ATTR(preset, "config:0-3"); -/* Sink ID - same for all ETMs */ -PMU_FORMAT_ATTR(sinkid, "config2:0-31"); /* config ID - set if a system configuration is selected */ PMU_FORMAT_ATTR(configid, "config2:32-63"); PMU_FORMAT_ATTR(cc_threshold, "config3:0-11"); - /* * contextid always traces the "PID". The PID is in CONTEXTIDR_EL1 * when the kernel is running at EL1; when the kernel is at EL2, @@ -82,27 +78,34 @@ static ssize_t format_attr_contextid_show(struct device *dev, { int pid_fmt = ETM_OPT_CTXTID; -#if IS_ENABLED(CONFIG_CORESIGHT_SOURCE_ETM4X) pid_fmt = is_kernel_in_hyp_mode() ? ETM_OPT_CTXTID2 : ETM_OPT_CTXTID; -#endif return sprintf(page, "config:%d\n", pid_fmt); } static struct device_attribute format_attr_contextid = __ATTR(contextid, 0444, format_attr_contextid_show, NULL); +#endif +/* + * ETMv3 only uses the first 3 attributes for programming itself (see + * ETM3X_SUPPORTED_OPTIONS). Sink ID is also supported for selecting a + * sink in both, but not used for configuring the ETM. The remaining + * attributes are ETMv4 specific. + */ static struct attribute *etm_config_formats_attr[] = { &format_attr_cycacc.attr, - &format_attr_contextid.attr, - &format_attr_contextid1.attr, - &format_attr_contextid2.attr, &format_attr_timestamp.attr, &format_attr_retstack.attr, &format_attr_sinkid.attr, +#if IS_ENABLED(CONFIG_CORESIGHT_SOURCE_ETM4X) + &format_attr_contextid.attr, + &format_attr_contextid1.attr, + &format_attr_contextid2.attr, &format_attr_preset.attr, &format_attr_configid.attr, &format_attr_branch_broadcast.attr, &format_attr_cc_threshold.attr, +#endif NULL, }; From 458db6257149f3959469c880de80eba3e2494479 Mon Sep 17 00:00:00 2001 From: James Clark Date: Fri, 28 Nov 2025 11:55:17 +0000 Subject: [PATCH 065/297] coresight: Define format attributes with GEN_PMU_FORMAT_ATTR() This allows us to define and consume them in a unified way in later commits. A lot of the existing code has open coded bit shifts or direct usage of whole config values which is error prone and hides which bits are in use and which are free. Reviewed-by: Leo Yan Reviewed-by: Mike Leach Tested-by: Leo Yan Signed-off-by: James Clark Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251128-james-cs-syncfreq-v8-5-4d319764cc58@linaro.org --- .../hwtracing/coresight/coresight-etm-perf.c | 21 +++++++------ .../hwtracing/coresight/coresight-etm-perf.h | 31 +++++++++++++++++++ 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm-perf.c b/drivers/hwtracing/coresight/coresight-etm-perf.c index 3805282b97e8..bf4b105e0f41 100644 --- a/drivers/hwtracing/coresight/coresight-etm-perf.c +++ b/drivers/hwtracing/coresight/coresight-etm-perf.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -50,22 +51,22 @@ struct etm_ctxt { static DEFINE_PER_CPU(struct etm_ctxt, etm_ctxt); static DEFINE_PER_CPU(struct coresight_device *, csdev_src); -PMU_FORMAT_ATTR(cycacc, "config:" __stringify(ETM_OPT_CYCACC)); -PMU_FORMAT_ATTR(timestamp, "config:" __stringify(ETM_OPT_TS)); -PMU_FORMAT_ATTR(retstack, "config:" __stringify(ETM_OPT_RETSTK)); -PMU_FORMAT_ATTR(sinkid, "config2:0-31"); +GEN_PMU_FORMAT_ATTR(cycacc); +GEN_PMU_FORMAT_ATTR(timestamp); +GEN_PMU_FORMAT_ATTR(retstack); +GEN_PMU_FORMAT_ATTR(sinkid); #if IS_ENABLED(CONFIG_CORESIGHT_SOURCE_ETM4X) -PMU_FORMAT_ATTR(branch_broadcast, "config:"__stringify(ETM_OPT_BRANCH_BROADCAST)); +GEN_PMU_FORMAT_ATTR(branch_broadcast); /* contextid1 enables tracing CONTEXTIDR_EL1*/ -PMU_FORMAT_ATTR(contextid1, "config:" __stringify(ETM_OPT_CTXTID)); +GEN_PMU_FORMAT_ATTR(contextid1); /* contextid2 enables tracing CONTEXTIDR_EL2*/ -PMU_FORMAT_ATTR(contextid2, "config:" __stringify(ETM_OPT_CTXTID2)); +GEN_PMU_FORMAT_ATTR(contextid2); /* preset - if sink ID is used as a configuration selector */ -PMU_FORMAT_ATTR(preset, "config:0-3"); +GEN_PMU_FORMAT_ATTR(preset); /* config ID - set if a system configuration is selected */ -PMU_FORMAT_ATTR(configid, "config2:32-63"); -PMU_FORMAT_ATTR(cc_threshold, "config3:0-11"); +GEN_PMU_FORMAT_ATTR(configid); +GEN_PMU_FORMAT_ATTR(cc_threshold); /* * contextid always traces the "PID". The PID is in CONTEXTIDR_EL1 diff --git a/drivers/hwtracing/coresight/coresight-etm-perf.h b/drivers/hwtracing/coresight/coresight-etm-perf.h index 5febbcdb8696..c794087a0e99 100644 --- a/drivers/hwtracing/coresight/coresight-etm-perf.h +++ b/drivers/hwtracing/coresight/coresight-etm-perf.h @@ -20,6 +20,37 @@ struct cscfg_config_desc; */ #define ETM_ADDR_CMP_MAX 8 +#define ATTR_CFG_FLD_preset_CFG config +#define ATTR_CFG_FLD_preset_LO 0 +#define ATTR_CFG_FLD_preset_HI 3 +#define ATTR_CFG_FLD_branch_broadcast_CFG config +#define ATTR_CFG_FLD_branch_broadcast_LO 8 +#define ATTR_CFG_FLD_branch_broadcast_HI 8 +#define ATTR_CFG_FLD_cycacc_CFG config +#define ATTR_CFG_FLD_cycacc_LO 12 +#define ATTR_CFG_FLD_cycacc_HI 12 +#define ATTR_CFG_FLD_contextid1_CFG config +#define ATTR_CFG_FLD_contextid1_LO 14 +#define ATTR_CFG_FLD_contextid1_HI 14 +#define ATTR_CFG_FLD_contextid2_CFG config +#define ATTR_CFG_FLD_contextid2_LO 15 +#define ATTR_CFG_FLD_contextid2_HI 15 +#define ATTR_CFG_FLD_timestamp_CFG config +#define ATTR_CFG_FLD_timestamp_LO 28 +#define ATTR_CFG_FLD_timestamp_HI 28 +#define ATTR_CFG_FLD_retstack_CFG config +#define ATTR_CFG_FLD_retstack_LO 29 +#define ATTR_CFG_FLD_retstack_HI 29 +#define ATTR_CFG_FLD_sinkid_CFG config2 +#define ATTR_CFG_FLD_sinkid_LO 0 +#define ATTR_CFG_FLD_sinkid_HI 31 +#define ATTR_CFG_FLD_configid_CFG config2 +#define ATTR_CFG_FLD_configid_LO 32 +#define ATTR_CFG_FLD_configid_HI 63 +#define ATTR_CFG_FLD_cc_threshold_CFG config3 +#define ATTR_CFG_FLD_cc_threshold_LO 0 +#define ATTR_CFG_FLD_cc_threshold_HI 11 + /** * struct etm_filter - single instruction range or start/stop configuration. * @start_addr: The address to start tracing on. From a1d19cd2b1a6c41783caa49fc28e89f434673198 Mon Sep 17 00:00:00 2001 From: James Clark Date: Fri, 28 Nov 2025 11:55:18 +0000 Subject: [PATCH 066/297] coresight: Interpret ETMv3 config with ATTR_CFG_GET_FLD() Currently we're programming attr->config directly into ETMCR after some validation. This obscures which fields are being used, and also makes it impossible to move fields around or use other configN fields in the future. Improve it by only reading the fields that are valid and then setting the appropriate ETMCR bits based on each one. The ETMCR_CTXID_SIZE part can be removed as it was never a valid option because it's not in ETM3X_SUPPORTED_OPTIONS. Reviewed-by: Leo Yan Reviewed-by: Mike Leach Tested-by: Leo Yan Signed-off-by: James Clark Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251128-james-cs-syncfreq-v8-6-4d319764cc58@linaro.org --- .../coresight/coresight-etm3x-core.c | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm3x-core.c b/drivers/hwtracing/coresight/coresight-etm3x-core.c index a5e809589d3e..4511fc2f8d72 100644 --- a/drivers/hwtracing/coresight/coresight-etm3x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm3x-core.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include "coresight-etm.h" @@ -339,21 +340,22 @@ static int etm_parse_event_config(struct etm_drvdata *drvdata, if (attr->config & ~ETM3X_SUPPORTED_OPTIONS) return -EINVAL; - config->ctrl = attr->config; + config->ctrl = 0; - /* Don't trace contextID when runs in non-root PID namespace */ - if (!task_is_in_init_pid_ns(current)) - config->ctrl &= ~ETMCR_CTXID_SIZE; + if (ATTR_CFG_GET_FLD(attr, cycacc)) + config->ctrl |= ETMCR_CYC_ACC; + + if (ATTR_CFG_GET_FLD(attr, timestamp)) + config->ctrl |= ETMCR_TIMESTAMP_EN; /* - * Possible to have cores with PTM (supports ret stack) and ETM - * (never has ret stack) on the same SoC. So if we have a request - * for return stack that can't be honoured on this core then - * clear the bit - trace will still continue normally + * Possible to have cores with PTM (supports ret stack) and ETM (never + * has ret stack) on the same SoC. So only enable when it can be honored + * - trace will still continue normally otherwise. */ - if ((config->ctrl & ETMCR_RETURN_STACK) && - !(drvdata->etmccer & ETMCCER_RETSTACK)) - config->ctrl &= ~ETMCR_RETURN_STACK; + if (ATTR_CFG_GET_FLD(attr, retstack) && + (drvdata->etmccer & ETMCCER_RETSTACK)) + config->ctrl |= ETMCR_RETURN_STACK; return 0; } From d633fd22e8100d4363b0c44e6e92150d670bab71 Mon Sep 17 00:00:00 2001 From: James Clark Date: Fri, 28 Nov 2025 11:55:19 +0000 Subject: [PATCH 067/297] coresight: Don't reject unrecognized ETMv3 format attributes config isn't the only field, there are also config1, config2, etc. Rejecting unrecognized attributes is therefore inconsistent as it wasn't done for all fields. It was only necessary when we were directly programming attr->config into ETMCR and didn't hide the unsupported fields, but now it's not needed so remove it. Reviewed-by: Leo Yan Reviewed-by: Mike Leach Tested-by: Leo Yan Signed-off-by: James Clark Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251128-james-cs-syncfreq-v8-7-4d319764cc58@linaro.org --- drivers/hwtracing/coresight/coresight-etm3x-core.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm3x-core.c b/drivers/hwtracing/coresight/coresight-etm3x-core.c index 4511fc2f8d72..584d653eda81 100644 --- a/drivers/hwtracing/coresight/coresight-etm3x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm3x-core.c @@ -333,13 +333,6 @@ static int etm_parse_event_config(struct etm_drvdata *drvdata, if (config->mode) etm_config_trace_mode(config); - /* - * At this time only cycle accurate, return stack and timestamp - * options are available. - */ - if (attr->config & ~ETM3X_SUPPORTED_OPTIONS) - return -EINVAL; - config->ctrl = 0; if (ATTR_CFG_GET_FLD(attr, cycacc)) From b945d3677754dc1031515d9cd1604d0b61bb9fa2 Mon Sep 17 00:00:00 2001 From: James Clark Date: Fri, 28 Nov 2025 11:55:20 +0000 Subject: [PATCH 068/297] coresight: Interpret perf config with ATTR_CFG_GET_FLD() The "config:" string construction in format_attr_contextid_show() can be removed because it either showed the existing context1 or context2 formats which have already been generated, so can be called themselves. The other conversions are straightforward replacements. Tested-by: Leo Yan Signed-off-by: James Clark Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251128-james-cs-syncfreq-v8-8-4d319764cc58@linaro.org --- .../hwtracing/coresight/coresight-etm-perf.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm-perf.c b/drivers/hwtracing/coresight/coresight-etm-perf.c index bf4b105e0f41..3c8a6f795094 100644 --- a/drivers/hwtracing/coresight/coresight-etm-perf.c +++ b/drivers/hwtracing/coresight/coresight-etm-perf.c @@ -77,10 +77,9 @@ static ssize_t format_attr_contextid_show(struct device *dev, struct device_attribute *attr, char *page) { - int pid_fmt = ETM_OPT_CTXTID; - - pid_fmt = is_kernel_in_hyp_mode() ? ETM_OPT_CTXTID2 : ETM_OPT_CTXTID; - return sprintf(page, "config:%d\n", pid_fmt); + if (is_kernel_in_hyp_mode()) + return contextid2_show(dev, attr, page); + return contextid1_show(dev, attr, page); } static struct device_attribute format_attr_contextid = @@ -319,7 +318,7 @@ static bool sinks_compatible(struct coresight_device *a, static void *etm_setup_aux(struct perf_event *event, void **pages, int nr_pages, bool overwrite) { - u32 id, cfg_hash; + u32 sink_hash, cfg_hash; int cpu = event->cpu; cpumask_t *mask; struct coresight_device *sink = NULL; @@ -332,13 +331,12 @@ static void *etm_setup_aux(struct perf_event *event, void **pages, INIT_WORK(&event_data->work, free_event_data); /* First get the selected sink from user space. */ - if (event->attr.config2 & GENMASK_ULL(31, 0)) { - id = (u32)event->attr.config2; - sink = user_sink = coresight_get_sink_by_id(id); - } + sink_hash = ATTR_CFG_GET_FLD(&event->attr, sinkid); + if (sink_hash) + sink = user_sink = coresight_get_sink_by_id(sink_hash); /* check if user wants a coresight configuration selected */ - cfg_hash = (u32)((event->attr.config2 & GENMASK_ULL(63, 32)) >> 32); + cfg_hash = ATTR_CFG_GET_FLD(&event->attr, configid); if (cfg_hash) { if (cscfg_activate_config(cfg_hash)) goto err; From afed86e6e141faa2c7433517c9d91ab4d7d9d443 Mon Sep 17 00:00:00 2001 From: James Clark Date: Fri, 28 Nov 2025 11:55:21 +0000 Subject: [PATCH 069/297] coresight: Interpret ETMv4 config with ATTR_CFG_GET_FLD() Remove hard coded bitfield extractions and shifts and replace with ATTR_CFG_GET_FLD(). ETM4_CFG_BIT_BB was defined to give the register bit positions to userspace, TRCCONFIGR_BB should be used in the kernel so replace it. Reviewed-by: Leo Yan Reviewed-by: Mike Leach Tested-by: Leo Yan Signed-off-by: James Clark Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251128-james-cs-syncfreq-v8-9-4d319764cc58@linaro.org --- .../coresight/coresight-etm4x-core.c | 44 ++++++++----------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c index 2ec2ae1fef58..b457f182efbe 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -780,17 +781,17 @@ static int etm4_parse_event_config(struct coresight_device *csdev, goto out; /* Go from generic option to ETMv4 specifics */ - if (attr->config & BIT(ETM_OPT_CYCACC)) { + if (ATTR_CFG_GET_FLD(attr, cycacc)) { config->cfg |= TRCCONFIGR_CCI; /* TRM: Must program this for cycacc to work */ - cc_threshold = attr->config3 & ETM_CYC_THRESHOLD_MASK; + cc_threshold = ATTR_CFG_GET_FLD(attr, cc_threshold); if (!cc_threshold) cc_threshold = ETM_CYC_THRESHOLD_DEFAULT; if (cc_threshold < drvdata->ccitmin) cc_threshold = drvdata->ccitmin; config->ccctlr = cc_threshold; } - if (attr->config & BIT(ETM_OPT_TS)) { + if (ATTR_CFG_GET_FLD(attr, timestamp)) { /* * Configure timestamps to be emitted at regular intervals in * order to correlate instructions executed on different CPUs @@ -810,17 +811,17 @@ static int etm4_parse_event_config(struct coresight_device *csdev, } /* Only trace contextID when runs in root PID namespace */ - if ((attr->config & BIT(ETM_OPT_CTXTID)) && + if (ATTR_CFG_GET_FLD(attr, contextid1) && task_is_in_init_pid_ns(current)) /* bit[6], Context ID tracing bit */ config->cfg |= TRCCONFIGR_CID; /* - * If set bit ETM_OPT_CTXTID2 in perf config, this asks to trace VMID - * for recording CONTEXTIDR_EL2. Do not enable VMID tracing if the - * kernel is not running in EL2. + * If set bit contextid2 in perf config, this asks to trace VMID for + * recording CONTEXTIDR_EL2. Do not enable VMID tracing if the kernel + * is not running in EL2. */ - if (attr->config & BIT(ETM_OPT_CTXTID2)) { + if (ATTR_CFG_GET_FLD(attr, contextid2)) { if (!is_kernel_in_hyp_mode()) { ret = -EINVAL; goto out; @@ -831,26 +832,22 @@ static int etm4_parse_event_config(struct coresight_device *csdev, } /* return stack - enable if selected and supported */ - if ((attr->config & BIT(ETM_OPT_RETSTK)) && drvdata->retstack) + if (ATTR_CFG_GET_FLD(attr, retstack) && drvdata->retstack) /* bit[12], Return stack enable bit */ config->cfg |= TRCCONFIGR_RS; /* - * Set any selected configuration and preset. - * - * This extracts the values of PMU_FORMAT_ATTR(configid) and PMU_FORMAT_ATTR(preset) - * in the perf attributes defined in coresight-etm-perf.c. - * configid uses bits 63:32 of attr->config2, preset uses bits 3:0 of attr->config. - * A zero configid means no configuration active, preset = 0 means no preset selected. + * Set any selected configuration and preset. A zero configid means no + * configuration active, preset = 0 means no preset selected. */ - if (attr->config2 & GENMASK_ULL(63, 32)) { - cfg_hash = (u32)(attr->config2 >> 32); - preset = attr->config & 0xF; + cfg_hash = ATTR_CFG_GET_FLD(attr, configid); + if (cfg_hash) { + preset = ATTR_CFG_GET_FLD(attr, preset); ret = cscfg_csdev_enable_active_config(csdev, cfg_hash, preset); } /* branch broadcast - enable if selected and supported */ - if (attr->config & BIT(ETM_OPT_BRANCH_BROADCAST)) { + if (ATTR_CFG_GET_FLD(attr, branch_broadcast)) { if (!drvdata->trcbb) { /* * Missing BB support could cause silent decode errors @@ -859,7 +856,7 @@ static int etm4_parse_event_config(struct coresight_device *csdev, ret = -EINVAL; goto out; } else { - config->cfg |= BIT(ETM4_CFG_BIT_BB); + config->cfg |= TRCCONFIGR_BB; } } @@ -1083,11 +1080,8 @@ static int etm4_disable_perf(struct coresight_device *csdev, return -EINVAL; etm4_disable_hw(drvdata); - /* - * The config_id occupies bits 63:32 of the config2 perf event attr - * field. If this is non-zero then we will have enabled a config. - */ - if (attr->config2 & GENMASK_ULL(63, 32)) + /* If configid is non-zero then we will have enabled a config. */ + if (ATTR_CFG_GET_FLD(attr, configid)) cscfg_csdev_disable_active_config(csdev); /* From 3285c471d0c0b991e5efc96c1a8bcc9ace17b9b8 Mon Sep 17 00:00:00 2001 From: James Clark Date: Fri, 28 Nov 2025 11:55:22 +0000 Subject: [PATCH 070/297] coresight: Remove misleading definitions ETM_OPT_* definitions duplicate the PMU format attributes that have always been published in sysfs. Hardcoding them here makes it misleading as to what the 'real' PMU API is and prevents attributes from being rearranged in the future. ETM4_CFG_BIT_* definitions just define what the Arm Architecture is which is not the responsibility of the kernel to do and doesn't scale to other registers or versions of ETM. It's not an actual software ABI/API and these definitions here mislead that it is. Any tools using the first ones would be broken anyway as they won't work when attributes are moved, so removing them is the right thing to do and will prompt a fix. Tools using the second ones can trivially redefine them locally. Perf also has its own copy of the headers so both of these things can be fixed up at a later date. Reviewed-by: Leo Yan Reviewed-by: Mike Leach Tested-by: Leo Yan Signed-off-by: James Clark Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251128-james-cs-syncfreq-v8-10-4d319764cc58@linaro.org --- include/linux/coresight-pmu.h | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/include/linux/coresight-pmu.h b/include/linux/coresight-pmu.h index 89b0ac0014b0..2e179abe472a 100644 --- a/include/linux/coresight-pmu.h +++ b/include/linux/coresight-pmu.h @@ -21,30 +21,6 @@ */ #define CORESIGHT_LEGACY_CPU_TRACE_ID(cpu) (0x10 + (cpu * 2)) -/* - * Below are the definition of bit offsets for perf option, and works as - * arbitrary values for all ETM versions. - * - * Most of them are orignally from ETMv3.5/PTM's ETMCR config, therefore, - * ETMv3.5/PTM doesn't define ETMCR config bits with prefix "ETM3_" and - * directly use below macros as config bits. - */ -#define ETM_OPT_BRANCH_BROADCAST 8 -#define ETM_OPT_CYCACC 12 -#define ETM_OPT_CTXTID 14 -#define ETM_OPT_CTXTID2 15 -#define ETM_OPT_TS 28 -#define ETM_OPT_RETSTK 29 - -/* ETMv4 CONFIGR programming bits for the ETM OPTs */ -#define ETM4_CFG_BIT_BB 3 -#define ETM4_CFG_BIT_CYCACC 4 -#define ETM4_CFG_BIT_CTXTID 6 -#define ETM4_CFG_BIT_VMID 7 -#define ETM4_CFG_BIT_TS 11 -#define ETM4_CFG_BIT_RETSTK 12 -#define ETM4_CFG_BIT_VMID_OPT 15 - /* * Interpretation of the PERF_RECORD_AUX_OUTPUT_HW_ID payload. * Used to associate a CPU with the CoreSight Trace ID. From f4d2f5fec06a900214166136d31bdc6fe8ee00d1 Mon Sep 17 00:00:00 2001 From: James Clark Date: Fri, 28 Nov 2025 11:55:23 +0000 Subject: [PATCH 071/297] coresight: Prepare to allow setting the timestamp interval Timestamps are currently emitted at the maximum rate possible, which is much too frequent for most use cases. In the next commit, the timestamp field will be widened to take a value, so set the interval using the value now. Granular control is not required, so save space in the config by interpreting it as 2 ^ timestamp. And then 4 bits (0 - 15) will be enough to set the interval to be larger than the existing SYNC timestamp interval. No sysfs mode support is needed for this attribute because counter generated timestamps are only configured for Perf mode. Reviewed-by: Leo Yan Tested-by: Leo Yan Signed-off-by: James Clark Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251128-james-cs-syncfreq-v8-11-4d319764cc58@linaro.org --- .../coresight/coresight-etm4x-core.c | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c index b457f182efbe..7e9c923acf4b 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c @@ -650,7 +650,7 @@ static void etm4_enable_sysfs_smp_call(void *info) * +--------------+ * | * +------v-------+ - * | Counter x | (reload to 1 on underflow) + * | Counter x | (reload to 2 ^ (ts_level - 1) on underflow) * +--------------+ * | * +------v--------------+ @@ -661,7 +661,8 @@ static void etm4_enable_sysfs_smp_call(void *info) * | Timestamp Generator | (timestamp on resource y) * +----------------------+ */ -static int etm4_config_timestamp_event(struct etmv4_drvdata *drvdata) +static int etm4_config_timestamp_event(struct etmv4_drvdata *drvdata, + u8 ts_level) { int ctridx; int rselector; @@ -703,12 +704,8 @@ static int etm4_config_timestamp_event(struct etmv4_drvdata *drvdata) return -ENOSPC; } - /* - * Initialise original and reload counter value to the smallest - * possible value in order to get as much precision as we can. - */ - config->cntr_val[ctridx] = 1; - config->cntrldvr[ctridx] = 1; + /* Initialise original and reload counter value. */ + config->cntr_val[ctridx] = config->cntrldvr[ctridx] = 1 << (ts_level - 1); /* * Trace Counter Control Register TRCCNTCTLRn @@ -756,6 +753,7 @@ static int etm4_parse_event_config(struct coresight_device *csdev, struct perf_event_attr *attr = &event->attr; unsigned long cfg_hash; int preset, cc_threshold; + u8 ts_level; /* Clear configuration from previous run */ memset(config, 0, sizeof(struct etmv4_config)); @@ -791,13 +789,15 @@ static int etm4_parse_event_config(struct coresight_device *csdev, cc_threshold = drvdata->ccitmin; config->ccctlr = cc_threshold; } - if (ATTR_CFG_GET_FLD(attr, timestamp)) { + + ts_level = ATTR_CFG_GET_FLD(attr, timestamp); + if (ts_level) { /* * Configure timestamps to be emitted at regular intervals in * order to correlate instructions executed on different CPUs * (CPU-wide trace scenarios). */ - ret = etm4_config_timestamp_event(drvdata); + ret = etm4_config_timestamp_event(drvdata, ts_level); /* * No need to go further if timestamp intervals can't From 6c75940eb76db6ed003eaf8cca296ade9dd1f46d Mon Sep 17 00:00:00 2001 From: James Clark Date: Fri, 28 Nov 2025 11:55:24 +0000 Subject: [PATCH 072/297] coresight: Extend width of timestamp format attribute 'timestamp' is currently 1 bit wide for on/off. To enable setting different intervals, extend it to 4 bits wide. Keep the old bit position for backward compatibility ("deprecated_timestamp") but don't publish in the format/ folder. It will be removed from the documentation and can be removed completely after enough time has passed. ETM3x doesn't support different intervals, so validate that the value is either 0 or 1. Tools that read the bit positions from the format/ folder will continue to work as before, setting either 0 or 1 for off/on. Tools that incorrectly didn't do this and set the ETM_OPT_TS bit directly will also continue to work because that old bit is still checked. This avoids adding a second timestamp attribute for setting the interval. This would be awkward to use because tools would have to be updated to ensure that the timestamps are always enabled when an interval is set, and the driver would have to validate that both options are provided together. All this does is implement the semantics of a single enum but spread over multiple fields. Reviewed-by: Leo Yan Tested-by: Leo Yan Tested-by: Jie Gan Signed-off-by: James Clark Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251128-james-cs-syncfreq-v8-12-4d319764cc58@linaro.org --- .../hwtracing/coresight/coresight-etm-perf.h | 13 ++++++--- .../coresight/coresight-etm3x-core.c | 12 ++++++++- .../coresight/coresight-etm4x-core.c | 27 +++++++++++-------- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm-perf.h b/drivers/hwtracing/coresight/coresight-etm-perf.h index c794087a0e99..24d929428633 100644 --- a/drivers/hwtracing/coresight/coresight-etm-perf.h +++ b/drivers/hwtracing/coresight/coresight-etm-perf.h @@ -23,6 +23,9 @@ struct cscfg_config_desc; #define ATTR_CFG_FLD_preset_CFG config #define ATTR_CFG_FLD_preset_LO 0 #define ATTR_CFG_FLD_preset_HI 3 +#define ATTR_CFG_FLD_timestamp_CFG config +#define ATTR_CFG_FLD_timestamp_LO 4 +#define ATTR_CFG_FLD_timestamp_HI 7 #define ATTR_CFG_FLD_branch_broadcast_CFG config #define ATTR_CFG_FLD_branch_broadcast_LO 8 #define ATTR_CFG_FLD_branch_broadcast_HI 8 @@ -35,9 +38,13 @@ struct cscfg_config_desc; #define ATTR_CFG_FLD_contextid2_CFG config #define ATTR_CFG_FLD_contextid2_LO 15 #define ATTR_CFG_FLD_contextid2_HI 15 -#define ATTR_CFG_FLD_timestamp_CFG config -#define ATTR_CFG_FLD_timestamp_LO 28 -#define ATTR_CFG_FLD_timestamp_HI 28 +/* + * Old position of 'timestamp' and not published in sysfs. Remove at a later + * date if necessary. + */ +#define ATTR_CFG_FLD_deprecated_timestamp_CFG config +#define ATTR_CFG_FLD_deprecated_timestamp_LO 28 +#define ATTR_CFG_FLD_deprecated_timestamp_HI 28 #define ATTR_CFG_FLD_retstack_CFG config #define ATTR_CFG_FLD_retstack_LO 29 #define ATTR_CFG_FLD_retstack_HI 29 diff --git a/drivers/hwtracing/coresight/coresight-etm3x-core.c b/drivers/hwtracing/coresight/coresight-etm3x-core.c index 584d653eda81..57e4a21c8fdd 100644 --- a/drivers/hwtracing/coresight/coresight-etm3x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm3x-core.c @@ -310,6 +310,7 @@ static int etm_parse_event_config(struct etm_drvdata *drvdata, { struct etm_config *config = &drvdata->config; struct perf_event_attr *attr = &event->attr; + u8 ts_level; if (!attr) return -EINVAL; @@ -338,7 +339,16 @@ static int etm_parse_event_config(struct etm_drvdata *drvdata, if (ATTR_CFG_GET_FLD(attr, cycacc)) config->ctrl |= ETMCR_CYC_ACC; - if (ATTR_CFG_GET_FLD(attr, timestamp)) + ts_level = max(ATTR_CFG_GET_FLD(attr, timestamp), + ATTR_CFG_GET_FLD(attr, deprecated_timestamp)); + + if (ts_level > 1) { + dev_dbg(&drvdata->csdev->dev, + "timestamp format attribute should be 0 (off) or 1 (on)\n"); + return -EINVAL; + } + + if (ts_level) config->ctrl |= ETMCR_TIMESTAMP_EN; /* diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c index 7e9c923acf4b..d565a73f0042 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c @@ -750,6 +750,9 @@ static int etm4_parse_event_config(struct coresight_device *csdev, int ret = 0; struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); struct etmv4_config *config = &drvdata->config; + struct perf_event_attr max_timestamp = { + .ATTR_CFG_FLD_timestamp_CFG = U64_MAX, + }; struct perf_event_attr *attr = &event->attr; unsigned long cfg_hash; int preset, cc_threshold; @@ -790,21 +793,23 @@ static int etm4_parse_event_config(struct coresight_device *csdev, config->ccctlr = cc_threshold; } - ts_level = ATTR_CFG_GET_FLD(attr, timestamp); + ts_level = max(ATTR_CFG_GET_FLD(attr, timestamp), + ATTR_CFG_GET_FLD(attr, deprecated_timestamp)); if (ts_level) { /* - * Configure timestamps to be emitted at regular intervals in - * order to correlate instructions executed on different CPUs - * (CPU-wide trace scenarios). + * Don't do counter generated timestamps when ts_level == MAX. + * Leave only SYNC timestamps from TRCCONFIGR_TS. */ - ret = etm4_config_timestamp_event(drvdata, ts_level); + if (ts_level != ATTR_CFG_GET_FLD(&max_timestamp, timestamp)) { + ret = etm4_config_timestamp_event(drvdata, ts_level); - /* - * No need to go further if timestamp intervals can't - * be configured. - */ - if (ret) - goto out; + /* + * Error if user asked for timestamps but there was no + * free counter. + */ + if (ret) + goto out; + } /* bit[11], Global timestamp tracing bit */ config->cfg |= TRCCONFIGR_TS; From 19214ad0a4e38c371013cca0b2ea584aa2bee8dd Mon Sep 17 00:00:00 2001 From: James Clark Date: Fri, 28 Nov 2025 11:55:25 +0000 Subject: [PATCH 073/297] coresight: docs: Document etm4x timestamp interval option Document how the new field is used, maximum value and the interaction with SYNC timestamps. Tested-by: Leo Yan Signed-off-by: James Clark Reviewed-by: Leo Yan Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251128-james-cs-syncfreq-v8-13-4d319764cc58@linaro.org --- Documentation/trace/coresight/coresight.rst | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Documentation/trace/coresight/coresight.rst b/Documentation/trace/coresight/coresight.rst index 806699871b80..d461de4e067e 100644 --- a/Documentation/trace/coresight/coresight.rst +++ b/Documentation/trace/coresight/coresight.rst @@ -613,8 +613,20 @@ They are also listed in the folder /sys/bus/event_source/devices/cs_etm/format/ - Session local version of the system wide setting: :ref:`ETM_MODE_RETURNSTACK ` * - timestamp - - Session local version of the system wide setting: :ref:`ETMv4_MODE_TIMESTAMP - ` + - Controls generation and interval of timestamps. + + 0 = off, 1 = minimum interval .. 15 = maximum interval. + + Values 1 - 14 use a counter that decrements every cycle to generate a + timestamp on underflow. The reload value for the counter is 2 ^ (interval + - 1). If the value is 1 then the reload value is 1, if the value is 11 + then the reload value is 1024 etc. + + Setting the maximum interval (15) will disable the counter generated + timestamps, freeing the counter resource, leaving only ones emitted when + a SYNC packet is generated. The sync interval is controlled with + TRCSYNCPR.PERIOD which is every 4096 bytes of trace by default. + * - cc_threshold - Cycle count threshold value. If nothing is provided here or the provided value is 0, then the default value i.e 0x100 will be used. If provided value is less than minimum cycles threshold From 1f4c9d8a1021281750c6cda126d6f8a40cc24e71 Mon Sep 17 00:00:00 2001 From: Navaneeth K Date: Thu, 27 Nov 2025 16:53:37 +0000 Subject: [PATCH 074/297] most: core: fix resource leak in most_register_interface error paths The function most_register_interface() did not correctly release resources if it failed early (before registering the device). In these cases, it returned an error code immediately, leaking the memory allocated for the interface. Fix this by initializing the device early via device_initialize() and calling put_device() on all error paths. The most_register_interface() is expected to call put_device() on error which frees the resources allocated in the caller. The put_device() either calls release_mdev() or dim2_release(), depending on the caller. Switch to using device_add() instead of device_register() to handle the split initialization. Acked-by: Abdun Nihaal Signed-off-by: Navaneeth K Reviewed-by: Dan Carpenter Link: https://patch.msgid.link/20251127165337.19172-1-knavaneeth786@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/most/core.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/most/core.c b/drivers/most/core.c index da319d108ea1..6277e6702ca8 100644 --- a/drivers/most/core.c +++ b/drivers/most/core.c @@ -1286,15 +1286,19 @@ int most_register_interface(struct most_interface *iface) !iface->poison_channel || (iface->num_channels > MAX_CHANNELS)) return -EINVAL; + device_initialize(iface->dev); + id = ida_alloc(&mdev_id, GFP_KERNEL); if (id < 0) { dev_err(iface->dev, "Failed to allocate device ID\n"); + put_device(iface->dev); return id; } iface->p = kzalloc(sizeof(*iface->p), GFP_KERNEL); if (!iface->p) { ida_free(&mdev_id, id); + put_device(iface->dev); return -ENOMEM; } @@ -1304,7 +1308,7 @@ int most_register_interface(struct most_interface *iface) iface->dev->bus = &mostbus; iface->dev->groups = interface_attr_groups; dev_set_drvdata(iface->dev, iface); - if (device_register(iface->dev)) { + if (device_add(iface->dev)) { dev_err(iface->dev, "Failed to register interface device\n"); kfree(iface->p); put_device(iface->dev); From 820c866c42de99e7d65c7bd1f591f067dbbe3cae Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Tue, 23 Dec 2025 19:05:26 -0800 Subject: [PATCH 075/297] mhi: host: Add support for loading dual ELF image format Currently, the FBC image contains a single ELF header followed by segments for both SBL and WLAN FW. However, TME-L (Trust Management Engine Lite) supported devices (e.g., QCC2072) require separate ELF headers for SBL and WLAN FW segments due to TME-L image authentication requirements. Current image format contains two sections in a single binary: - First 512KB: ELF header + SBL segments - Remaining: WLAN FW segments (raw data) The TME-L supported image format contains two complete ELF files in a single binary: - First 512KB: Complete SBL ELF file (ELF header + SBL segments) - Remaining: Complete WLAN FW ELF file (ELF header + WLAN FW segments) Download behavior: - Legacy: 1. First 512KB via BHI (ELF header + SBL) 2. Full image via BHIe - TME-L: 1. First 512KB via BHI (SBL ELF file) 2. Remaining via BHIe (WLAN FW ELF file only) Add runtime detection to automatically identify the image format by checking for the presence of a second ELF header at the 512KB boundary. When detected, MHI skips the first 512KB during WLAN FW download over BHIe as it is loaded in BHI phase. Signed-off-by: Qiang Yu Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20251223-wlan_image_load_skip_512k-v5-1-8d4459d720b5@oss.qualcomm.com --- drivers/bus/mhi/host/boot.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/bus/mhi/host/boot.c b/drivers/bus/mhi/host/boot.c index 205d83ac069f..c76db35bc29a 100644 --- a/drivers/bus/mhi/host/boot.c +++ b/drivers/bus/mhi/host/boot.c @@ -584,6 +584,16 @@ skip_req_fw: * device transitioning into MHI READY state */ if (fw_load_type == MHI_FW_LOAD_FBC) { + /* + * Some FW combine two separate ELF images (SBL + WLAN FW) in a single + * file. Hence, check for the existence of the second ELF header after + * SBL. If present, load the second image separately. + */ + if (!memcmp(fw_data + mhi_cntrl->sbl_size, ELFMAG, SELFMAG)) { + fw_data += mhi_cntrl->sbl_size; + fw_sz -= mhi_cntrl->sbl_size; + } + ret = mhi_alloc_bhie_table(mhi_cntrl, &mhi_cntrl->fbc_image, fw_sz); if (ret) { release_firmware(firmware); From 4ba12d304175fd7b2d2089899e38428db2d1b189 Mon Sep 17 00:00:00 2001 From: Ariana Lazar Date: Tue, 16 Dec 2025 14:05:50 +0200 Subject: [PATCH 076/297] dt-bindings: iio: dac: adding support for Microchip MCP47FEB02 This is the device tree schema for iio driver for Microchip MCP47F(E/V)B(0/1/2)1, MCP47F(E/V)B(0/1/2)2, MCP47F(E/V)B(0/1/2)4 and MCP47F(E/V)B(0/1/2)8 series of buffered voltage output Digital-to-Analog Converters with nonvolatile or volatile memory and an I2C Interface. The families support up to 8 output channels. The devices can be 8-bit, 10-bit and 12-bit. Signed-off-by: Ariana Lazar Reviewed-by: Conor Dooley Signed-off-by: Jonathan Cameron --- .../iio/dac/microchip,mcp47feb02.yaml | 302 ++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 308 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/dac/microchip,mcp47feb02.yaml diff --git a/Documentation/devicetree/bindings/iio/dac/microchip,mcp47feb02.yaml b/Documentation/devicetree/bindings/iio/dac/microchip,mcp47feb02.yaml new file mode 100644 index 000000000000..d2466aa6bda2 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/dac/microchip,mcp47feb02.yaml @@ -0,0 +1,302 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/dac/microchip,mcp47feb02.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microchip MCP47F(E/V)B(0/1/2)(1/2/4/8) DAC with I2C Interface Families + +maintainers: + - Ariana Lazar + +description: | + Datasheet for MCP47FEB01, MCP47FEB11, MCP47FEB21, MCP47FEB02, MCP47FEB12, + MCP47FEB22 can be found here: + https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005375A.pdf + Datasheet for MCP47FVB01, MCP47FVB11, MCP47FVB21, MCP47FVB02, MCP47FVB12, + MCP47FVB22 can be found here: + https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005405A.pdf + Datasheet for MCP47FEB04, MCP47FEB14, MCP47FEB24, MCP47FEB08, MCP47FEB18, + MCP47FEB28, MCP47FVB04, MCP47FVB14, MCP47FVB24, MCP47FVB08, MCP47FVB18, + MCP47FVB28 can be found here: + https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP47FXBX48-Data-Sheet-DS200006368A.pdf + + +------------+--------------+-------------+-------------+------------+ + | Device | Resolution | Channels | Vref number | Memory | + |------------|--------------|-------------|-------------|------------| + | MCP47FEB01 | 8-bit | 1 | 1 | EEPROM | + | MCP47FEB11 | 10-bit | 1 | 1 | EEPROM | + | MCP47FEB21 | 12-bit | 1 | 1 | EEPROM | + |------------|--------------|-------------|-------------|------------| + | MCP47FEB02 | 8-bit | 2 | 1 | EEPROM | + | MCP47FEB12 | 10-bit | 2 | 1 | EEPROM | + | MCP47FEB22 | 12-bit | 2 | 1 | EEPROM | + |------------|--------------|-------------|-------------|------------| + | MCP47FVB01 | 8-bit | 1 | 1 | RAM | + | MCP47FVB11 | 10-bit | 1 | 1 | RAM | + | MCP47FVB21 | 12-bit | 1 | 1 | RAM | + |------------|--------------|-------------|-------------|------------| + | MCP47FVB02 | 8-bit | 2 | 1 | RAM | + | MCP47FVB12 | 10-bit | 2 | 1 | RAM | + | MCP47FVB22 | 12-bit | 2 | 1 | RAM | + |------------|--------------|-------------|-------------|------------| + | MCP47FVB04 | 8-bit | 4 | 2 | RAM | + | MCP47FVB14 | 10-bit | 4 | 2 | RAM | + | MCP47FVB24 | 12-bit | 4 | 2 | RAM | + |------------|--------------|-------------|-------------|------------| + | MCP47FVB08 | 8-bit | 8 | 2 | RAM | + | MCP47FVB18 | 10-bit | 8 | 2 | RAM | + | MCP47FVB28 | 12-bit | 8 | 2 | RAM | + |------------|--------------|-------------|-------------|------------| + | MCP47FEB04 | 8-bit | 4 | 2 | EEPROM | + | MCP47FEB14 | 10-bit | 4 | 2 | EEPROM | + | MCP47FEB24 | 12-bit | 4 | 2 | EEPROM | + |------------|--------------|-------------|-------------|------------| + | MCP47FEB08 | 8-bit | 8 | 2 | EEPROM | + | MCP47FEB18 | 10-bit | 8 | 2 | EEPROM | + | MCP47FEB28 | 12-bit | 8 | 2 | EEPROM | + +------------+--------------+-------------+-------------+------------+ + +properties: + compatible: + enum: + - microchip,mcp47feb01 + - microchip,mcp47feb11 + - microchip,mcp47feb21 + - microchip,mcp47feb02 + - microchip,mcp47feb12 + - microchip,mcp47feb22 + - microchip,mcp47fvb01 + - microchip,mcp47fvb11 + - microchip,mcp47fvb21 + - microchip,mcp47fvb02 + - microchip,mcp47fvb12 + - microchip,mcp47fvb22 + - microchip,mcp47fvb04 + - microchip,mcp47fvb14 + - microchip,mcp47fvb24 + - microchip,mcp47fvb08 + - microchip,mcp47fvb18 + - microchip,mcp47fvb28 + - microchip,mcp47feb04 + - microchip,mcp47feb14 + - microchip,mcp47feb24 + - microchip,mcp47feb08 + - microchip,mcp47feb18 + - microchip,mcp47feb28 + + reg: + maxItems: 1 + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + vdd-supply: + description: + Provides power to the chip and it could be used as reference voltage. The + voltage is used to calculate scale. For parts without EEPROM at powerup + this will be the selected as voltage reference. + + vref-supply: + description: | + Vref pin (it could be found as Vref0 into the datasheet) may be used as a + voltage reference when this supply is specified. The internal reference + will be taken into account for voltage reference besides VDD if this supply + does not exist. + + This supply will be voltage reference for the following outputs: + - for single-channel device: Vout0; + - for dual-channel device: Vout0, Vout1; + - for quad-channel device: Vout0, Vout2; + - for octal-channel device: Vout0, Vout2, Vout6, Vout8; + + vref1-supply: + description: | + Vref1 pin may be used as a voltage reference when this supply is specified. + The internal reference will be taken into account for voltage reference + beside VDD if this supply does not exist. + + This supply will be voltage reference for the following outputs: + - for quad-channel device: Vout1, Vout3; + - for octal-channel device: Vout1, Vout3, Vout5, Vout7; + + lat-gpios: + description: + LAT pin to be used as a hardware trigger to synchronously update the DAC + channels. The pin is active Low. It could be also found as LAT0 in + datasheet. + maxItems: 1 + + lat1-gpios: + description: + LAT1 pin to be used as a hardware trigger to synchronously update the odd + DAC channels on devices with 4 and 8 channels. The pin is active Low. + maxItems: 1 + + microchip,vref-buffered: + type: boolean + description: + Enable buffering of the external Vref/Vref0 pin in cases where the + external reference voltage does not have sufficient current capability in + order not to drop it’s voltage when connected to the internal resistor + ladder circuit. + + microchip,vref1-buffered: + type: boolean + description: + Enable buffering of the external Vref1 pin in cases where the external + reference voltage does not have sufficient current capability in order not + to drop it’s voltage when connected to the internal resistor ladder + circuit. + +patternProperties: + "^channel@[0-7]$": + $ref: dac.yaml + type: object + description: Voltage output channel. + + properties: + reg: + description: The channel number. + minItems: 1 + maxItems: 8 + + label: + description: Unique name to identify which channel this is. + + required: + - reg + + unevaluatedProperties: false + +required: + - compatible + - reg + - vdd-supply + +allOf: + - if: + properties: + compatible: + contains: + enum: + - microchip,mcp47feb01 + - microchip,mcp47feb11 + - microchip,mcp47feb21 + - microchip,mcp47fvb01 + - microchip,mcp47fvb11 + - microchip,mcp47fvb21 + then: + properties: + lat1-gpios: false + vref1-supply: false + microchip,vref1-buffered: false + channel@0: + properties: + reg: + const: 0 + patternProperties: + "^channel@[1-7]$": false + - if: + properties: + compatible: + contains: + enum: + - microchip,mcp47feb02 + - microchip,mcp47feb12 + - microchip,mcp47feb22 + - microchip,mcp47fvb02 + - microchip,mcp47fvb12 + - microchip,mcp47fvb22 + then: + properties: + lat1-gpios: false + vref1-supply: false + microchip,vref1-buffered: false + patternProperties: + "^channel@[0-1]$": + properties: + reg: + enum: [0, 1] + "^channel@[2-7]$": false + - if: + properties: + compatible: + contains: + enum: + - microchip,mcp47fvb04 + - microchip,mcp47fvb14 + - microchip,mcp47fvb24 + - microchip,mcp47feb04 + - microchip,mcp47feb14 + - microchip,mcp47feb24 + then: + patternProperties: + "^channel@[0-3]$": + properties: + reg: + enum: [0, 1, 2, 3] + "^channel@[4-7]$": false + - if: + properties: + compatible: + contains: + enum: + - microchip,mcp47fvb08 + - microchip,mcp47fvb18 + - microchip,mcp47fvb28 + - microchip,mcp47feb08 + - microchip,mcp47feb18 + - microchip,mcp47feb28 + then: + patternProperties: + "^channel@[0-7]$": + properties: + reg: + enum: [0, 1, 2, 3, 4, 5, 6, 7] + - if: + not: + required: + - vref-supply + then: + properties: + microchip,vref-buffered: false + - if: + not: + required: + - vref1-supply + then: + properties: + microchip,vref1-buffered: false + +additionalProperties: false + +examples: + - | + i2c { + + #address-cells = <1>; + #size-cells = <0>; + dac@0 { + compatible = "microchip,mcp47feb02"; + reg = <0>; + vdd-supply = <&vdac_vdd>; + vref-supply = <&vref_reg>; + + #address-cells = <1>; + #size-cells = <0>; + channel@0 { + reg = <0>; + label = "Adjustable_voltage_ch0"; + }; + + channel@1 { + reg = <0x1>; + label = "Adjustable_voltage_ch1"; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 9075de044d18..3a965c4f7798 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15655,6 +15655,12 @@ F: Documentation/ABI/testing/sysfs-bus-iio-potentiometer-mcp4531 F: drivers/iio/potentiometer/mcp4018.c F: drivers/iio/potentiometer/mcp4531.c +MCP47FEB02 MICROCHIP DAC DRIVER +M: Ariana Lazar +L: linux-iio@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/iio/dac/microchip,mcp47feb02.yaml + MCP4821 DAC DRIVER M: Anshul Dalal L: linux-iio@vger.kernel.org From c0fef45dbab06238e96e221f7c0a8fd2d569f7dd Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Mon, 24 Nov 2025 11:39:52 +0100 Subject: [PATCH 077/297] char/mwave: drop it When I tried to clean up the driver a bit, Arnd noted: > According to thinkwiki.de, the 3780i modem was only used in a > couple of Thinkpad models that are now over 25 years old, using > Pentium II processors, and they all have a physical RS232 port > that can be used to connect an external modem instead. > > Maybe we can just retire this driver? So instead of the clean up, drop the driver altogether. Signed-off-by: Jiri Slaby (SUSE) Suggested-by: Arnd Bergmann Link: https://lore.kernel.org/all/b8834e5d-fdde-4b1a-8757-288dddc507a9@app.fastmail.com/ Link: https://patch.msgid.link/20251124103952.995229-1-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- Documentation/admin-guide/devices.txt | 2 +- .../userspace-api/ioctl/ioctl-number.rst | 1 - drivers/char/Kconfig | 26 - drivers/char/Makefile | 1 - drivers/char/mwave/3780i.c | 536 ------------------ drivers/char/mwave/3780i.h | 358 ------------ drivers/char/mwave/Makefile | 10 - drivers/char/mwave/README | 37 -- drivers/char/mwave/mwavedd.c | 432 -------------- drivers/char/mwave/mwavedd.h | 90 --- drivers/char/mwave/mwavepub.h | 89 --- drivers/char/mwave/smapi.c | 404 ------------- drivers/char/mwave/smapi.h | 76 --- drivers/char/mwave/tp3780i.c | 477 ---------------- drivers/char/mwave/tp3780i.h | 103 ---- include/linux/miscdevice.h | 1 - 16 files changed, 1 insertion(+), 2642 deletions(-) delete mode 100644 drivers/char/mwave/3780i.c delete mode 100644 drivers/char/mwave/3780i.h delete mode 100644 drivers/char/mwave/Makefile delete mode 100644 drivers/char/mwave/README delete mode 100644 drivers/char/mwave/mwavedd.c delete mode 100644 drivers/char/mwave/mwavedd.h delete mode 100644 drivers/char/mwave/mwavepub.h delete mode 100644 drivers/char/mwave/smapi.c delete mode 100644 drivers/char/mwave/smapi.h delete mode 100644 drivers/char/mwave/tp3780i.c delete mode 100644 drivers/char/mwave/tp3780i.h diff --git a/Documentation/admin-guide/devices.txt b/Documentation/admin-guide/devices.txt index 94c98be1329a..0fbde7d8dc78 100644 --- a/Documentation/admin-guide/devices.txt +++ b/Documentation/admin-guide/devices.txt @@ -352,7 +352,7 @@ 216 = /dev/fujitsu/apanel Fujitsu/Siemens application panel 217 = /dev/ni/natmotn National Instruments Motion 218 = /dev/kchuid Inter-process chuid control - 219 = /dev/modems/mwave MWave modem firmware upload + 219 = 220 = /dev/mptctl Message passing technology (MPT) control 221 = /dev/mvista/hssdsi Montavista PICMG hot swap system driver 222 = /dev/mvista/hasi Montavista PICMG high availability diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index 7232b3544cec..6fbd9fbcc43f 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -397,7 +397,6 @@ Code Seq# Include File Comments 0xCD 01 linux/reiserfs_fs.h Dead since 6.13 0xCE 01-02 uapi/linux/cxl_mem.h Compute Express Link Memory Devices 0xCF 02 fs/smb/client/cifs_ioctl.h -0xDB 00-0F drivers/char/mwave/mwavepub.h 0xDD 00-3F ZFCP device driver see drivers/s390/scsi/ 0xE5 00-3F linux/fuse.h diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index d2cfc584e202..2a3a37b2cf3c 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -249,32 +249,6 @@ config SONYPI To compile this driver as a module, choose M here: the module will be called sonypi. -config MWAVE - tristate "ACP Modem (Mwave) support" - depends on X86 && TTY - select SERIAL_8250 - help - The ACP modem (Mwave) for Linux is a WinModem. It is composed of a - kernel driver and a user level application. Together these components - support direct attachment to public switched telephone networks (PSTNs) - and support selected world wide countries. - - This version of the ACP Modem driver supports the IBM Thinkpad 600E, - 600, and 770 that include on board ACP modem hardware. - - The modem also supports the standard communications port interface - (ttySx) and is compatible with the Hayes AT Command Set. - - The user level application needed to use this driver can be found at - the IBM Linux Technology Center (LTC) web site: - . - - If you own one of the above IBM Thinkpads which has the Mwave chipset - in it, say Y. - - To compile this driver as a module, choose M here: the - module will be called mwave. - config SCx200_GPIO tristate "NatSemi SCx200 GPIO Support" depends on SCx200 diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 1291369b9126..47bdc882797a 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -33,7 +33,6 @@ obj-$(CONFIG_PC8736x_GPIO) += pc8736x_gpio.o obj-$(CONFIG_NSC_GPIO) += nsc_gpio.o obj-$(CONFIG_TELCLOCK) += tlclk.o -obj-$(CONFIG_MWAVE) += mwave/ obj-y += agp/ obj-$(CONFIG_HANGCHECK_TIMER) += hangcheck-timer.o diff --git a/drivers/char/mwave/3780i.c b/drivers/char/mwave/3780i.c deleted file mode 100644 index 90f93cefb21c..000000000000 --- a/drivers/char/mwave/3780i.c +++ /dev/null @@ -1,536 +0,0 @@ -/* -* -* 3780i.c -- helper routines for the 3780i DSP -* -* -* Written By: Mike Sullivan IBM Corporation -* -* Copyright (C) 1999 IBM Corporation -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* NO WARRANTY -* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR -* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT -* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, -* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is -* solely responsible for determining the appropriateness of using and -* distributing the Program and assumes all risks associated with its -* exercise of rights under this Agreement, including but not limited to -* the risks and costs of program errors, damage to or loss of data, -* programs or equipment, and unavailability or interruption of operations. -* -* DISCLAIMER OF LIABILITY -* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -* -* -* 10/23/2000 - Alpha Release -* First release to the public -*/ - -#define pr_fmt(fmt) "3780i: " fmt - -#include -#include -#include -#include -#include -#include /* cond_resched() */ - -#include -#include -#include -#include "smapi.h" -#include "mwavedd.h" -#include "3780i.h" - -static DEFINE_SPINLOCK(dsp_lock); - -static void PaceMsaAccess(unsigned short usDspBaseIO) -{ - cond_resched(); - udelay(100); - cond_resched(); -} - -unsigned short dsp3780I_ReadMsaCfg(unsigned short usDspBaseIO, - unsigned long ulMsaAddr) -{ - unsigned long flags; - unsigned short val; - - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulMsaAddr); - OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulMsaAddr >> 16)); - val = InWordDsp(DSP_MsaDataDSISHigh); - spin_unlock_irqrestore(&dsp_lock, flags); - - return val; -} - -void dsp3780I_WriteMsaCfg(unsigned short usDspBaseIO, - unsigned long ulMsaAddr, unsigned short usValue) -{ - unsigned long flags; - - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulMsaAddr); - OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulMsaAddr >> 16)); - OutWordDsp(DSP_MsaDataDSISHigh, usValue); - spin_unlock_irqrestore(&dsp_lock, flags); -} - -static void dsp3780I_WriteGenCfg(unsigned short usDspBaseIO, unsigned uIndex, - unsigned char ucValue) -{ - DSP_ISA_SLAVE_CONTROL rSlaveControl; - DSP_ISA_SLAVE_CONTROL rSlaveControl_Save; - - MKBYTE(rSlaveControl) = InByteDsp(DSP_IsaSlaveControl); - - rSlaveControl_Save = rSlaveControl; - rSlaveControl.ConfigMode = true; - - OutByteDsp(DSP_IsaSlaveControl, MKBYTE(rSlaveControl)); - OutByteDsp(DSP_ConfigAddress, (unsigned char) uIndex); - OutByteDsp(DSP_ConfigData, ucValue); - OutByteDsp(DSP_IsaSlaveControl, MKBYTE(rSlaveControl_Save)); -} - -int dsp3780I_EnableDSP(struct dsp_3780i_config_settings *pSettings, - unsigned short *pIrqMap, - unsigned short *pDmaMap) -{ - unsigned long flags; - unsigned short usDspBaseIO = pSettings->usDspBaseIO; - int i; - DSP_UART_CFG_1 rUartCfg1; - DSP_UART_CFG_2 rUartCfg2; - DSP_HBRIDGE_CFG_1 rHBridgeCfg1; - DSP_HBRIDGE_CFG_2 rHBridgeCfg2; - DSP_BUSMASTER_CFG_1 rBusmasterCfg1; - DSP_BUSMASTER_CFG_2 rBusmasterCfg2; - DSP_ISA_PROT_CFG rIsaProtCfg; - DSP_POWER_MGMT_CFG rPowerMgmtCfg; - DSP_HBUS_TIMER_CFG rHBusTimerCfg; - DSP_LBUS_TIMEOUT_DISABLE rLBusTimeoutDisable; - DSP_CHIP_RESET rChipReset; - DSP_CLOCK_CONTROL_1 rClockControl1; - DSP_CLOCK_CONTROL_2 rClockControl2; - DSP_ISA_SLAVE_CONTROL rSlaveControl; - DSP_HBRIDGE_CONTROL rHBridgeControl; - unsigned short tval; - - if (!pSettings->bDSPEnabled) { - pr_err("%s: Error: DSP not enabled. Aborting.\n", __func__); - return -EIO; - } - - if (pSettings->bModemEnabled) { - rUartCfg1.Reserved = rUartCfg2.Reserved = 0; - rUartCfg1.IrqActiveLow = pSettings->bUartIrqActiveLow; - rUartCfg1.IrqPulse = pSettings->bUartIrqPulse; - rUartCfg1.Irq = - (unsigned char) pIrqMap[pSettings->usUartIrq]; - switch (pSettings->usUartBaseIO) { - case 0x03F8: - rUartCfg1.BaseIO = 0; - break; - case 0x02F8: - rUartCfg1.BaseIO = 1; - break; - case 0x03E8: - rUartCfg1.BaseIO = 2; - break; - case 0x02E8: - rUartCfg1.BaseIO = 3; - break; - } - rUartCfg2.Enable = true; - } - - rHBridgeCfg1.Reserved = rHBridgeCfg2.Reserved = 0; - rHBridgeCfg1.IrqActiveLow = pSettings->bDspIrqActiveLow; - rHBridgeCfg1.IrqPulse = pSettings->bDspIrqPulse; - rHBridgeCfg1.Irq = (unsigned char) pIrqMap[pSettings->usDspIrq]; - rHBridgeCfg1.AccessMode = 1; - rHBridgeCfg2.Enable = true; - - - rBusmasterCfg2.Reserved = 0; - rBusmasterCfg1.Dma = (unsigned char) pDmaMap[pSettings->usDspDma]; - rBusmasterCfg1.NumTransfers = - (unsigned char) pSettings->usNumTransfers; - rBusmasterCfg1.ReRequest = (unsigned char) pSettings->usReRequest; - rBusmasterCfg1.MEMCS16 = pSettings->bEnableMEMCS16; - rBusmasterCfg2.IsaMemCmdWidth = - (unsigned char) pSettings->usIsaMemCmdWidth; - - - rIsaProtCfg.Reserved = 0; - rIsaProtCfg.GateIOCHRDY = pSettings->bGateIOCHRDY; - - rPowerMgmtCfg.Reserved = 0; - rPowerMgmtCfg.Enable = pSettings->bEnablePwrMgmt; - - rHBusTimerCfg.LoadValue = - (unsigned char) pSettings->usHBusTimerLoadValue; - - rLBusTimeoutDisable.Reserved = 0; - rLBusTimeoutDisable.DisableTimeout = - pSettings->bDisableLBusTimeout; - - MKWORD(rChipReset) = ~pSettings->usChipletEnable; - - rClockControl1.Reserved1 = rClockControl1.Reserved2 = 0; - rClockControl1.N_Divisor = pSettings->usN_Divisor; - rClockControl1.M_Multiplier = pSettings->usM_Multiplier; - - rClockControl2.Reserved = 0; - rClockControl2.PllBypass = pSettings->bPllBypass; - - /* Issue a soft reset to the chip */ - /* Note: Since we may be coming in with 3780i clocks suspended, we must keep - * soft-reset active for 10ms. - */ - rSlaveControl.ClockControl = 0; - rSlaveControl.SoftReset = true; - rSlaveControl.ConfigMode = false; - rSlaveControl.Reserved = 0; - - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_IsaSlaveControl, MKWORD(rSlaveControl)); - MKWORD(tval) = InWordDsp(DSP_IsaSlaveControl); - - for (i = 0; i < 11; i++) - udelay(2000); - - rSlaveControl.SoftReset = false; - OutWordDsp(DSP_IsaSlaveControl, MKWORD(rSlaveControl)); - - MKWORD(tval) = InWordDsp(DSP_IsaSlaveControl); - - /* Program our general configuration registers */ - WriteGenCfg(DSP_HBridgeCfg1Index, MKBYTE(rHBridgeCfg1)); - WriteGenCfg(DSP_HBridgeCfg2Index, MKBYTE(rHBridgeCfg2)); - WriteGenCfg(DSP_BusMasterCfg1Index, MKBYTE(rBusmasterCfg1)); - WriteGenCfg(DSP_BusMasterCfg2Index, MKBYTE(rBusmasterCfg2)); - WriteGenCfg(DSP_IsaProtCfgIndex, MKBYTE(rIsaProtCfg)); - WriteGenCfg(DSP_PowerMgCfgIndex, MKBYTE(rPowerMgmtCfg)); - WriteGenCfg(DSP_HBusTimerCfgIndex, MKBYTE(rHBusTimerCfg)); - - if (pSettings->bModemEnabled) { - WriteGenCfg(DSP_UartCfg1Index, MKBYTE(rUartCfg1)); - WriteGenCfg(DSP_UartCfg2Index, MKBYTE(rUartCfg2)); - } - - - rHBridgeControl.EnableDspInt = false; - rHBridgeControl.MemAutoInc = true; - rHBridgeControl.IoAutoInc = false; - rHBridgeControl.DiagnosticMode = false; - - OutWordDsp(DSP_HBridgeControl, MKWORD(rHBridgeControl)); - spin_unlock_irqrestore(&dsp_lock, flags); - WriteMsaCfg(DSP_LBusTimeoutDisable, MKWORD(rLBusTimeoutDisable)); - WriteMsaCfg(DSP_ClockControl_1, MKWORD(rClockControl1)); - WriteMsaCfg(DSP_ClockControl_2, MKWORD(rClockControl2)); - WriteMsaCfg(DSP_ChipReset, MKWORD(rChipReset)); - - ReadMsaCfg(DSP_ChipID); - - return 0; -} - -int dsp3780I_DisableDSP(struct dsp_3780i_config_settings *pSettings) -{ - unsigned long flags; - unsigned short usDspBaseIO = pSettings->usDspBaseIO; - DSP_ISA_SLAVE_CONTROL rSlaveControl; - - rSlaveControl.ClockControl = 0; - rSlaveControl.SoftReset = true; - rSlaveControl.ConfigMode = false; - rSlaveControl.Reserved = 0; - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_IsaSlaveControl, MKWORD(rSlaveControl)); - - udelay(5); - - rSlaveControl.ClockControl = 1; - OutWordDsp(DSP_IsaSlaveControl, MKWORD(rSlaveControl)); - spin_unlock_irqrestore(&dsp_lock, flags); - - udelay(5); - - return 0; -} - -int dsp3780I_Reset(struct dsp_3780i_config_settings *pSettings) -{ - unsigned long flags; - unsigned short usDspBaseIO = pSettings->usDspBaseIO; - DSP_BOOT_DOMAIN rBootDomain; - DSP_HBRIDGE_CONTROL rHBridgeControl; - - spin_lock_irqsave(&dsp_lock, flags); - /* Mask DSP to PC interrupt */ - MKWORD(rHBridgeControl) = InWordDsp(DSP_HBridgeControl); - - rHBridgeControl.EnableDspInt = false; - OutWordDsp(DSP_HBridgeControl, MKWORD(rHBridgeControl)); - spin_unlock_irqrestore(&dsp_lock, flags); - - /* Reset the core via the boot domain register */ - rBootDomain.ResetCore = true; - rBootDomain.Halt = true; - rBootDomain.NMI = true; - rBootDomain.Reserved = 0; - - WriteMsaCfg(DSP_MspBootDomain, MKWORD(rBootDomain)); - - /* Reset all the chiplets and then reactivate them */ - WriteMsaCfg(DSP_ChipReset, 0xFFFF); - udelay(5); - WriteMsaCfg(DSP_ChipReset, - (unsigned short) (~pSettings->usChipletEnable)); - - return 0; -} - - -int dsp3780I_Run(struct dsp_3780i_config_settings *pSettings) -{ - unsigned long flags; - unsigned short usDspBaseIO = pSettings->usDspBaseIO; - DSP_BOOT_DOMAIN rBootDomain; - DSP_HBRIDGE_CONTROL rHBridgeControl; - - /* Transition the core to a running state */ - rBootDomain.ResetCore = true; - rBootDomain.Halt = false; - rBootDomain.NMI = true; - rBootDomain.Reserved = 0; - WriteMsaCfg(DSP_MspBootDomain, MKWORD(rBootDomain)); - - udelay(5); - - rBootDomain.ResetCore = false; - WriteMsaCfg(DSP_MspBootDomain, MKWORD(rBootDomain)); - udelay(5); - - rBootDomain.NMI = false; - WriteMsaCfg(DSP_MspBootDomain, MKWORD(rBootDomain)); - udelay(5); - - /* Enable DSP to PC interrupt */ - spin_lock_irqsave(&dsp_lock, flags); - MKWORD(rHBridgeControl) = InWordDsp(DSP_HBridgeControl); - rHBridgeControl.EnableDspInt = true; - - OutWordDsp(DSP_HBridgeControl, MKWORD(rHBridgeControl)); - spin_unlock_irqrestore(&dsp_lock, flags); - - return 0; -} - - -int dsp3780I_ReadDStore(unsigned short usDspBaseIO, void __user *pvBuffer, - unsigned uCount, unsigned long ulDSPAddr) -{ - unsigned long flags; - unsigned short __user *pusBuffer = pvBuffer; - unsigned short val; - - /* Set the initial MSA address. No adjustments need to be made to data store addresses */ - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulDSPAddr); - OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulDSPAddr >> 16)); - spin_unlock_irqrestore(&dsp_lock, flags); - - /* Transfer the memory block */ - while (uCount-- != 0) { - spin_lock_irqsave(&dsp_lock, flags); - val = InWordDsp(DSP_MsaDataDSISHigh); - spin_unlock_irqrestore(&dsp_lock, flags); - if(put_user(val, pusBuffer++)) - return -EFAULT; - - PaceMsaAccess(usDspBaseIO); - } - - return 0; -} - -int dsp3780I_ReadAndClearDStore(unsigned short usDspBaseIO, - void __user *pvBuffer, unsigned uCount, - unsigned long ulDSPAddr) -{ - unsigned long flags; - unsigned short __user *pusBuffer = pvBuffer; - unsigned short val; - - /* Set the initial MSA address. No adjustments need to be made to data store addresses */ - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulDSPAddr); - OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulDSPAddr >> 16)); - spin_unlock_irqrestore(&dsp_lock, flags); - - /* Transfer the memory block */ - while (uCount-- != 0) { - spin_lock_irqsave(&dsp_lock, flags); - val = InWordDsp(DSP_ReadAndClear); - spin_unlock_irqrestore(&dsp_lock, flags); - if(put_user(val, pusBuffer++)) - return -EFAULT; - - PaceMsaAccess(usDspBaseIO); - } - - return 0; -} - - -int dsp3780I_WriteDStore(unsigned short usDspBaseIO, void __user *pvBuffer, - unsigned uCount, unsigned long ulDSPAddr) -{ - unsigned long flags; - unsigned short __user *pusBuffer = pvBuffer; - - /* Set the initial MSA address. No adjustments need to be made to data store addresses */ - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulDSPAddr); - OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulDSPAddr >> 16)); - spin_unlock_irqrestore(&dsp_lock, flags); - - /* Transfer the memory block */ - while (uCount-- != 0) { - unsigned short val; - if(get_user(val, pusBuffer++)) - return -EFAULT; - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_MsaDataDSISHigh, val); - spin_unlock_irqrestore(&dsp_lock, flags); - - PaceMsaAccess(usDspBaseIO); - } - - return 0; -} - - -int dsp3780I_ReadIStore(unsigned short usDspBaseIO, void __user *pvBuffer, - unsigned uCount, unsigned long ulDSPAddr) -{ - unsigned long flags; - unsigned short __user *pusBuffer = pvBuffer; - - /* - * Set the initial MSA address. To convert from an instruction store - * address to an MSA address - * shift the address two bits to the left and set bit 22 - */ - ulDSPAddr = (ulDSPAddr << 2) | (1 << 22); - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulDSPAddr); - OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulDSPAddr >> 16)); - spin_unlock_irqrestore(&dsp_lock, flags); - - /* Transfer the memory block */ - while (uCount-- != 0) { - unsigned short val_lo, val_hi; - spin_lock_irqsave(&dsp_lock, flags); - val_lo = InWordDsp(DSP_MsaDataISLow); - val_hi = InWordDsp(DSP_MsaDataDSISHigh); - spin_unlock_irqrestore(&dsp_lock, flags); - if(put_user(val_lo, pusBuffer++)) - return -EFAULT; - if(put_user(val_hi, pusBuffer++)) - return -EFAULT; - - PaceMsaAccess(usDspBaseIO); - - } - - return 0; -} - - -int dsp3780I_WriteIStore(unsigned short usDspBaseIO, void __user *pvBuffer, - unsigned uCount, unsigned long ulDSPAddr) -{ - unsigned long flags; - unsigned short __user *pusBuffer = pvBuffer; - - /* - * Set the initial MSA address. To convert from an instruction store - * address to an MSA address - * shift the address two bits to the left and set bit 22 - */ - ulDSPAddr = (ulDSPAddr << 2) | (1 << 22); - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulDSPAddr); - OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulDSPAddr >> 16)); - spin_unlock_irqrestore(&dsp_lock, flags); - - /* Transfer the memory block */ - while (uCount-- != 0) { - unsigned short val_lo, val_hi; - if(get_user(val_lo, pusBuffer++)) - return -EFAULT; - if(get_user(val_hi, pusBuffer++)) - return -EFAULT; - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_MsaDataISLow, val_lo); - OutWordDsp(DSP_MsaDataDSISHigh, val_hi); - spin_unlock_irqrestore(&dsp_lock, flags); - - PaceMsaAccess(usDspBaseIO); - } - - return 0; -} - - -int dsp3780I_GetIPCSource(unsigned short usDspBaseIO, - unsigned short *pusIPCSource) -{ - unsigned long flags; - DSP_HBRIDGE_CONTROL rHBridgeControl; - - /* - * Disable DSP to PC interrupts, read the interrupt register, - * clear the pending IPC bits, and reenable DSP to PC interrupts - */ - spin_lock_irqsave(&dsp_lock, flags); - MKWORD(rHBridgeControl) = InWordDsp(DSP_HBridgeControl); - rHBridgeControl.EnableDspInt = false; - OutWordDsp(DSP_HBridgeControl, MKWORD(rHBridgeControl)); - - *pusIPCSource = InWordDsp(DSP_Interrupt); - OutWordDsp(DSP_Interrupt, (unsigned short) ~(*pusIPCSource)); - - rHBridgeControl.EnableDspInt = true; - OutWordDsp(DSP_HBridgeControl, MKWORD(rHBridgeControl)); - spin_unlock_irqrestore(&dsp_lock, flags); - - return 0; -} diff --git a/drivers/char/mwave/3780i.h b/drivers/char/mwave/3780i.h deleted file mode 100644 index 53dafceb20e0..000000000000 --- a/drivers/char/mwave/3780i.h +++ /dev/null @@ -1,358 +0,0 @@ -/* -* -* 3780i.h -- declarations for 3780i.c -* -* -* Written By: Mike Sullivan IBM Corporation -* -* Copyright (C) 1999 IBM Corporation -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* NO WARRANTY -* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR -* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT -* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, -* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is -* solely responsible for determining the appropriateness of using and -* distributing the Program and assumes all risks associated with its -* exercise of rights under this Agreement, including but not limited to -* the risks and costs of program errors, damage to or loss of data, -* programs or equipment, and unavailability or interruption of operations. -* -* DISCLAIMER OF LIABILITY -* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -* -* -* 10/23/2000 - Alpha Release -* First release to the public -*/ - -#ifndef _LINUX_3780I_H -#define _LINUX_3780I_H - -#include - -/* DSP I/O port offsets and definitions */ -#define DSP_IsaSlaveControl 0x0000 /* ISA slave control register */ -#define DSP_IsaSlaveStatus 0x0001 /* ISA slave status register */ -#define DSP_ConfigAddress 0x0002 /* General config address register */ -#define DSP_ConfigData 0x0003 /* General config data register */ -#define DSP_HBridgeControl 0x0002 /* HBridge control register */ -#define DSP_MsaAddrLow 0x0004 /* MSP System Address, low word */ -#define DSP_MsaAddrHigh 0x0006 /* MSP System Address, high word */ -#define DSP_MsaDataDSISHigh 0x0008 /* MSA data register: d-store word or high byte of i-store */ -#define DSP_MsaDataISLow 0x000A /* MSA data register: low word of i-store */ -#define DSP_ReadAndClear 0x000C /* MSA read and clear data register */ -#define DSP_Interrupt 0x000E /* Interrupt register (IPC source) */ - -typedef struct { - unsigned char ClockControl:1; /* RW: Clock control: 0=normal, 1=stop 3780i clocks */ - unsigned char SoftReset:1; /* RW: Soft reset 0=normal, 1=soft reset active */ - unsigned char ConfigMode:1; /* RW: Configuration mode, 0=normal, 1=config mode */ - unsigned short Reserved:13; /* 0: Reserved */ -} DSP_ISA_SLAVE_CONTROL; - - -typedef struct { - unsigned short EnableDspInt:1; /* RW: Enable DSP to X86 ISA interrupt 0=mask it, 1=enable it */ - unsigned short MemAutoInc:1; /* RW: Memory address auto increment, 0=disable, 1=enable */ - unsigned short IoAutoInc:1; /* RW: I/O address auto increment, 0=disable, 1=enable */ - unsigned short DiagnosticMode:1; /* RW: Disgnostic mode 0=nromal, 1=diagnostic mode */ - unsigned short IsaPacingTimer:12; /* R: ISA access pacing timer: count of core cycles stolen */ -} DSP_HBRIDGE_CONTROL; - - -/* DSP register indexes used with the configuration register address (index) register */ -#define DSP_UartCfg1Index 0x0003 /* UART config register 1 */ -#define DSP_UartCfg2Index 0x0004 /* UART config register 2 */ -#define DSP_HBridgeCfg1Index 0x0007 /* HBridge config register 1 */ -#define DSP_HBridgeCfg2Index 0x0008 /* HBridge config register 2 */ -#define DSP_BusMasterCfg1Index 0x0009 /* ISA bus master config register 1 */ -#define DSP_BusMasterCfg2Index 0x000A /* ISA bus master config register 2 */ -#define DSP_IsaProtCfgIndex 0x000F /* ISA protocol control register */ -#define DSP_PowerMgCfgIndex 0x0010 /* Low poser suspend/resume enable */ -#define DSP_HBusTimerCfgIndex 0x0011 /* HBUS timer load value */ - -typedef struct { - unsigned char IrqActiveLow:1; /* RW: IRQ active high or low: 0=high, 1=low */ - unsigned char IrqPulse:1; /* RW: IRQ pulse or level: 0=level, 1=pulse */ - unsigned char Irq:3; /* RW: IRQ selection */ - unsigned char BaseIO:2; /* RW: Base I/O selection */ - unsigned char Reserved:1; /* 0: Reserved */ -} DSP_UART_CFG_1; - -typedef struct { - unsigned char Enable:1; /* RW: Enable I/O and IRQ: 0=false, 1=true */ - unsigned char Reserved:7; /* 0: Reserved */ -} DSP_UART_CFG_2; - -typedef struct { - unsigned char IrqActiveLow:1; /* RW: IRQ active high=0 or low=1 */ - unsigned char IrqPulse:1; /* RW: IRQ pulse=1 or level=0 */ - unsigned char Irq:3; /* RW: IRQ selection */ - unsigned char AccessMode:1; /* RW: 16-bit register access method 0=byte, 1=word */ - unsigned char Reserved:2; /* 0: Reserved */ -} DSP_HBRIDGE_CFG_1; - -typedef struct { - unsigned char Enable:1; /* RW: enable I/O and IRQ: 0=false, 1=true */ - unsigned char Reserved:7; /* 0: Reserved */ -} DSP_HBRIDGE_CFG_2; - - -typedef struct { - unsigned char Dma:3; /* RW: DMA channel selection */ - unsigned char NumTransfers:2; /* RW: Maximum # of transfers once being granted the ISA bus */ - unsigned char ReRequest:2; /* RW: Minimum delay between releasing the ISA bus and requesting it again */ - unsigned char MEMCS16:1; /* RW: ISA signal MEMCS16: 0=disabled, 1=enabled */ -} DSP_BUSMASTER_CFG_1; - -typedef struct { - unsigned char IsaMemCmdWidth:2; /* RW: ISA memory command width */ - unsigned char Reserved:6; /* 0: Reserved */ -} DSP_BUSMASTER_CFG_2; - - -typedef struct { - unsigned char GateIOCHRDY:1; /* RW: Enable IOCHRDY gating: 0=false, 1=true */ - unsigned char Reserved:7; /* 0: Reserved */ -} DSP_ISA_PROT_CFG; - -typedef struct { - unsigned char Enable:1; /* RW: Enable low power suspend/resume 0=false, 1=true */ - unsigned char Reserved:7; /* 0: Reserved */ -} DSP_POWER_MGMT_CFG; - -typedef struct { - unsigned char LoadValue:8; /* RW: HBUS timer load value */ -} DSP_HBUS_TIMER_CFG; - - - -/* DSP registers that exist in MSA I/O space */ -#define DSP_ChipID 0x80000000 -#define DSP_MspBootDomain 0x80000580 -#define DSP_LBusTimeoutDisable 0x80000580 -#define DSP_ClockControl_1 0x8000058A -#define DSP_ClockControl_2 0x8000058C -#define DSP_ChipReset 0x80000588 -#define DSP_GpioModeControl_15_8 0x80000082 -#define DSP_GpioDriverEnable_15_8 0x80000076 -#define DSP_GpioOutputData_15_8 0x80000072 - -typedef struct { - unsigned short NMI:1; /* RW: non maskable interrupt */ - unsigned short Halt:1; /* RW: Halt MSP clock */ - unsigned short ResetCore:1; /* RW: Reset MSP core interface */ - unsigned short Reserved:13; /* 0: Reserved */ -} DSP_BOOT_DOMAIN; - -typedef struct { - unsigned short DisableTimeout:1; /* RW: Disable LBus timeout */ - unsigned short Reserved:15; /* 0: Reserved */ -} DSP_LBUS_TIMEOUT_DISABLE; - -typedef struct { - unsigned short Memory:1; /* RW: Reset memory interface */ - unsigned short SerialPort1:1; /* RW: Reset serial port 1 interface */ - unsigned short SerialPort2:1; /* RW: Reset serial port 2 interface */ - unsigned short SerialPort3:1; /* RW: Reset serial port 3 interface */ - unsigned short Gpio:1; /* RW: Reset GPIO interface */ - unsigned short Dma:1; /* RW: Reset DMA interface */ - unsigned short SoundBlaster:1; /* RW: Reset soundblaster interface */ - unsigned short Uart:1; /* RW: Reset UART interface */ - unsigned short Midi:1; /* RW: Reset MIDI interface */ - unsigned short IsaMaster:1; /* RW: Reset ISA master interface */ - unsigned short Reserved:6; /* 0: Reserved */ -} DSP_CHIP_RESET; - -typedef struct { - unsigned short N_Divisor:6; /* RW: (N) PLL output clock divisor */ - unsigned short Reserved1:2; /* 0: reserved */ - unsigned short M_Multiplier:6; /* RW: (M) PLL feedback clock multiplier */ - unsigned short Reserved2:2; /* 0: reserved */ -} DSP_CLOCK_CONTROL_1; - -typedef struct { - unsigned short PllBypass:1; /* RW: PLL Bypass */ - unsigned short Reserved:15; /* 0: Reserved */ -} DSP_CLOCK_CONTROL_2; - -typedef struct { - unsigned short Latch8:1; - unsigned short Latch9:1; - unsigned short Latch10:1; - unsigned short Latch11:1; - unsigned short Latch12:1; - unsigned short Latch13:1; - unsigned short Latch14:1; - unsigned short Latch15:1; - unsigned short Mask8:1; - unsigned short Mask9:1; - unsigned short Mask10:1; - unsigned short Mask11:1; - unsigned short Mask12:1; - unsigned short Mask13:1; - unsigned short Mask14:1; - unsigned short Mask15:1; -} DSP_GPIO_OUTPUT_DATA_15_8; - -typedef struct { - unsigned short Enable8:1; - unsigned short Enable9:1; - unsigned short Enable10:1; - unsigned short Enable11:1; - unsigned short Enable12:1; - unsigned short Enable13:1; - unsigned short Enable14:1; - unsigned short Enable15:1; - unsigned short Mask8:1; - unsigned short Mask9:1; - unsigned short Mask10:1; - unsigned short Mask11:1; - unsigned short Mask12:1; - unsigned short Mask13:1; - unsigned short Mask14:1; - unsigned short Mask15:1; -} DSP_GPIO_DRIVER_ENABLE_15_8; - -typedef struct { - unsigned short GpioMode8:2; - unsigned short GpioMode9:2; - unsigned short GpioMode10:2; - unsigned short GpioMode11:2; - unsigned short GpioMode12:2; - unsigned short GpioMode13:2; - unsigned short GpioMode14:2; - unsigned short GpioMode15:2; -} DSP_GPIO_MODE_15_8; - -/* Component masks that are defined in dspmgr.h */ -#define MW_ADC_MASK 0x0001 -#define MW_AIC2_MASK 0x0006 -#define MW_MIDI_MASK 0x0008 -#define MW_CDDAC_MASK 0x8001 -#define MW_AIC1_MASK 0xE006 -#define MW_UART_MASK 0xE00A -#define MW_ACI_MASK 0xE00B - -/* -* Definition of 3780i configuration structure. Unless otherwise stated, -* these values are provided as input to the 3780i support layer. At present, -* the only values maintained by the 3780i support layer are the saved UART -* registers. -*/ -struct dsp_3780i_config_settings { - - /* Location of base configuration register */ - unsigned short usBaseConfigIO; - - /* Enables for various DSP components */ - int bDSPEnabled; - int bModemEnabled; - int bInterruptClaimed; - - /* IRQ, DMA, and Base I/O addresses for various DSP components */ - unsigned short usDspIrq; - unsigned short usDspDma; - unsigned short usDspBaseIO; - unsigned short usUartIrq; - unsigned short usUartBaseIO; - - /* IRQ modes for various DSP components */ - int bDspIrqActiveLow; - int bUartIrqActiveLow; - int bDspIrqPulse; - int bUartIrqPulse; - - /* Card abilities */ - unsigned uIps; - unsigned uDStoreSize; - unsigned uIStoreSize; - unsigned uDmaBandwidth; - - /* Adapter specific 3780i settings */ - unsigned short usNumTransfers; - unsigned short usReRequest; - int bEnableMEMCS16; - unsigned short usIsaMemCmdWidth; - int bGateIOCHRDY; - int bEnablePwrMgmt; - unsigned short usHBusTimerLoadValue; - int bDisableLBusTimeout; - unsigned short usN_Divisor; - unsigned short usM_Multiplier; - int bPllBypass; - unsigned short usChipletEnable; /* Used with the chip reset register to enable specific chiplets */ - - /* Saved UART registers. These are maintained by the 3780i support layer. */ - int bUartSaved; /* True after a successful save of the UART registers */ - unsigned char ucIER; /* Interrupt enable register */ - unsigned char ucFCR; /* FIFO control register */ - unsigned char ucLCR; /* Line control register */ - unsigned char ucMCR; /* Modem control register */ - unsigned char ucSCR; /* Scratch register */ - unsigned char ucDLL; /* Divisor latch, low byte */ - unsigned char ucDLM; /* Divisor latch, high byte */ -}; - - -/* 3780i support functions */ -int dsp3780I_EnableDSP(struct dsp_3780i_config_settings *pSettings, - unsigned short *pIrqMap, - unsigned short *pDmaMap); -int dsp3780I_DisableDSP(struct dsp_3780i_config_settings *pSettings); -int dsp3780I_Reset(struct dsp_3780i_config_settings *pSettings); -int dsp3780I_Run(struct dsp_3780i_config_settings *pSettings); -int dsp3780I_ReadDStore(unsigned short usDspBaseIO, void __user *pvBuffer, - unsigned uCount, unsigned long ulDSPAddr); -int dsp3780I_ReadAndClearDStore(unsigned short usDspBaseIO, - void __user *pvBuffer, unsigned uCount, - unsigned long ulDSPAddr); -int dsp3780I_WriteDStore(unsigned short usDspBaseIO, void __user *pvBuffer, - unsigned uCount, unsigned long ulDSPAddr); -int dsp3780I_ReadIStore(unsigned short usDspBaseIO, void __user *pvBuffer, - unsigned uCount, unsigned long ulDSPAddr); -int dsp3780I_WriteIStore(unsigned short usDspBaseIO, void __user *pvBuffer, - unsigned uCount, unsigned long ulDSPAddr); -unsigned short dsp3780I_ReadMsaCfg(unsigned short usDspBaseIO, - unsigned long ulMsaAddr); -void dsp3780I_WriteMsaCfg(unsigned short usDspBaseIO, - unsigned long ulMsaAddr, unsigned short usValue); -int dsp3780I_GetIPCSource(unsigned short usDspBaseIO, - unsigned short *pusIPCSource); - -/* I/O port access macros */ -#define MKWORD(var) (*((unsigned short *)(&var))) -#define MKBYTE(var) (*((unsigned char *)(&var))) - -#define WriteMsaCfg(addr,value) dsp3780I_WriteMsaCfg(usDspBaseIO,addr,value) -#define ReadMsaCfg(addr) dsp3780I_ReadMsaCfg(usDspBaseIO,addr) -#define WriteGenCfg(index,value) dsp3780I_WriteGenCfg(usDspBaseIO,index,value) -#define ReadGenCfg(index) dsp3780I_ReadGenCfg(usDspBaseIO,index) - -#define InWordDsp(index) inw(usDspBaseIO+index) -#define InByteDsp(index) inb(usDspBaseIO+index) -#define OutWordDsp(index,value) outw(value,usDspBaseIO+index) -#define OutByteDsp(index,value) outb(value,usDspBaseIO+index) - -#endif diff --git a/drivers/char/mwave/Makefile b/drivers/char/mwave/Makefile deleted file mode 100644 index e56c1a375535..000000000000 --- a/drivers/char/mwave/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# -# Makefile for ACP Modem (Mwave). -# -# See the README file in this directory for more info. -# - -obj-$(CONFIG_MWAVE) += mwave.o - -mwave-y := mwavedd.o smapi.o tp3780i.o 3780i.o diff --git a/drivers/char/mwave/README b/drivers/char/mwave/README deleted file mode 100644 index 6224aa814c62..000000000000 --- a/drivers/char/mwave/README +++ /dev/null @@ -1,37 +0,0 @@ -Module options --------------- - -The mwave module takes the following options. Note that these options -are not saved by the BIOS and so do not persist after unload and reload. - - mwave_3780i_irq=5/7/10/11/15 - If the dsp irq has not been setup and stored in bios by the - thinkpad configuration utility then this parameter allows the - irq used by the dsp to be configured. - - mwave_3780i_io=0x130/0x350/0x0070/0xDB0 - If the dsp io range has not been setup and stored in bios by the - thinkpad configuration utility then this parameter allows the - io range used by the dsp to be configured. - - mwave_uart_irq=3/4 - If the mwave's uart irq has not been setup and stored in bios by the - thinkpad configuration utility then this parameter allows the - irq used by the mwave uart to be configured. - - mwave_uart_io=0x3f8/0x2f8/0x3E8/0x2E8 - If the uart io range has not been setup and stored in bios by the - thinkpad configuration utility then this parameter allows the - io range used by the mwave uart to be configured. - -Example to enable the 3780i DSP using ttyS1 resources: - - insmod mwave mwave_3780i_irq=10 mwave_3780i_io=0x0130 mwave_uart_irq=3 mwave_uart_io=0x2f8 - -Accessing the driver --------------------- - -You must also create a node for the driver: - mkdir -p /dev/modems - mknod --mode=660 /dev/modems/mwave c 10 219 - diff --git a/drivers/char/mwave/mwavedd.c b/drivers/char/mwave/mwavedd.c deleted file mode 100644 index 640a9cb0dd8d..000000000000 --- a/drivers/char/mwave/mwavedd.c +++ /dev/null @@ -1,432 +0,0 @@ -/* -* -* mwavedd.c -- mwave device driver -* -* -* Written By: Mike Sullivan IBM Corporation -* -* Copyright (C) 1999 IBM Corporation -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* NO WARRANTY -* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR -* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT -* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, -* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is -* solely responsible for determining the appropriateness of using and -* distributing the Program and assumes all risks associated with its -* exercise of rights under this Agreement, including but not limited to -* the risks and costs of program errors, damage to or loss of data, -* programs or equipment, and unavailability or interruption of operations. -* -* DISCLAIMER OF LIABILITY -* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -* -* -* 10/23/2000 - Alpha Release -* First release to the public -*/ - -#define pr_fmt(fmt) "mwavedd: " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "smapi.h" -#include "mwavedd.h" -#include "3780i.h" -#include "tp3780i.h" - -MODULE_DESCRIPTION("3780i Advanced Communications Processor (Mwave) driver"); -MODULE_AUTHOR("Mike Sullivan and Paul Schroeder"); -MODULE_LICENSE("GPL"); - -/* -* These parameters support the setting of MWave resources. Note that no -* checks are made against other devices (ie. superio) for conflicts. -* We'll depend on users using the tpctl utility to do that for now -*/ -static DEFINE_MUTEX(mwave_mutex); -int mwave_3780i_irq = 0; -int mwave_3780i_io = 0; -int mwave_uart_irq = 0; -int mwave_uart_io = 0; -module_param_hw(mwave_3780i_irq, int, irq, 0); -module_param_hw(mwave_3780i_io, int, ioport, 0); -module_param_hw(mwave_uart_irq, int, irq, 0); -module_param_hw(mwave_uart_io, int, ioport, 0); - -struct mwave_device_data mwave_s_mdd; - -static long mwave_ioctl(struct file *file, unsigned int iocmd, - unsigned long ioarg) -{ - unsigned int retval = 0; - struct mwave_device_data *pDrvData = &mwave_s_mdd; - void __user *arg = (void __user *)ioarg; - - switch (iocmd) { - - case IOCTL_MW_RESET: - mutex_lock(&mwave_mutex); - retval = tp3780I_ResetDSP(&pDrvData->rBDData); - mutex_unlock(&mwave_mutex); - break; - - case IOCTL_MW_RUN: - mutex_lock(&mwave_mutex); - retval = tp3780I_StartDSP(&pDrvData->rBDData); - mutex_unlock(&mwave_mutex); - break; - - case IOCTL_MW_DSP_ABILITIES: { - struct mw_abilities rAbilities; - - mutex_lock(&mwave_mutex); - retval = tp3780I_QueryAbilities(&pDrvData->rBDData, - &rAbilities); - mutex_unlock(&mwave_mutex); - if (retval == 0) { - if (copy_to_user(arg, &rAbilities, sizeof(rAbilities))) - return -EFAULT; - } - } - break; - - case IOCTL_MW_READ_DATA: - case IOCTL_MW_READCLEAR_DATA: { - struct mw_readwrite rReadData; - unsigned short __user *pusBuffer = NULL; - - if( copy_from_user(&rReadData, arg, - sizeof(struct mw_readwrite)) ) - return -EFAULT; - pusBuffer = (unsigned short __user *) (rReadData.pBuf); - - mutex_lock(&mwave_mutex); - retval = tp3780I_ReadWriteDspDStore(&pDrvData->rBDData, - iocmd, - pusBuffer, - rReadData.ulDataLength, - rReadData.usDspAddress); - mutex_unlock(&mwave_mutex); - } - break; - - case IOCTL_MW_READ_INST: { - struct mw_readwrite rReadData; - unsigned short __user *pusBuffer = NULL; - - if (copy_from_user(&rReadData, arg, sizeof(rReadData))) - return -EFAULT; - pusBuffer = (unsigned short __user *) (rReadData.pBuf); - - mutex_lock(&mwave_mutex); - retval = tp3780I_ReadWriteDspDStore(&pDrvData->rBDData, - iocmd, pusBuffer, - rReadData.ulDataLength / 2, - rReadData.usDspAddress); - mutex_unlock(&mwave_mutex); - } - break; - - case IOCTL_MW_WRITE_DATA: { - struct mw_readwrite rWriteData; - unsigned short __user *pusBuffer = NULL; - - if (copy_from_user(&rWriteData, arg, sizeof(rWriteData))) - return -EFAULT; - pusBuffer = (unsigned short __user *) (rWriteData.pBuf); - - mutex_lock(&mwave_mutex); - retval = tp3780I_ReadWriteDspDStore(&pDrvData->rBDData, - iocmd, pusBuffer, - rWriteData.ulDataLength, - rWriteData.usDspAddress); - mutex_unlock(&mwave_mutex); - } - break; - - case IOCTL_MW_WRITE_INST: { - struct mw_readwrite rWriteData; - unsigned short __user *pusBuffer = NULL; - - if (copy_from_user(&rWriteData, arg, sizeof(rWriteData))) - return -EFAULT; - pusBuffer = (unsigned short __user *)(rWriteData.pBuf); - - mutex_lock(&mwave_mutex); - retval = tp3780I_ReadWriteDspIStore(&pDrvData->rBDData, - iocmd, pusBuffer, - rWriteData.ulDataLength, - rWriteData.usDspAddress); - mutex_unlock(&mwave_mutex); - } - break; - - case IOCTL_MW_REGISTER_IPC: { - unsigned int ipcnum = (unsigned int) ioarg; - - if (ipcnum >= ARRAY_SIZE(pDrvData->IPCs)) { - pr_err("%s: IOCTL_MW_REGISTER_IPC: Error: Invalid ipcnum %x\n", - __func__, ipcnum); - return -EINVAL; - } - ipcnum = array_index_nospec(ipcnum, - ARRAY_SIZE(pDrvData->IPCs)); - - mutex_lock(&mwave_mutex); - pDrvData->IPCs[ipcnum].bIsHere = false; - pDrvData->IPCs[ipcnum].bIsEnabled = true; - mutex_unlock(&mwave_mutex); - } - break; - - case IOCTL_MW_GET_IPC: { - unsigned int ipcnum = (unsigned int) ioarg; - - if (ipcnum >= ARRAY_SIZE(pDrvData->IPCs)) { - pr_err("%s: IOCTL_MW_GET_IPC: Error: Invalid ipcnum %x\n", __func__, - ipcnum); - return -EINVAL; - } - ipcnum = array_index_nospec(ipcnum, - ARRAY_SIZE(pDrvData->IPCs)); - - mutex_lock(&mwave_mutex); - if (pDrvData->IPCs[ipcnum].bIsEnabled == true) { - DECLARE_WAITQUEUE(wait, current); - - add_wait_queue(&pDrvData->IPCs[ipcnum].ipc_wait_queue, &wait); - pDrvData->IPCs[ipcnum].bIsHere = true; - set_current_state(TASK_INTERRUPTIBLE); - /* check whether an event was signalled by */ - /* the interrupt handler while we were gone */ - if (pDrvData->IPCs[ipcnum].usIntCount == 1) { /* first int has occurred (race condition) */ - pDrvData->IPCs[ipcnum].usIntCount = 2; /* first int has been handled */ - } else { /* either 1st int has not yet occurred, or we have already handled the first int */ - schedule(); - if (pDrvData->IPCs[ipcnum].usIntCount == 1) { - pDrvData->IPCs[ipcnum].usIntCount = 2; - } - } - pDrvData->IPCs[ipcnum].bIsHere = false; - remove_wait_queue(&pDrvData->IPCs[ipcnum].ipc_wait_queue, &wait); - set_current_state(TASK_RUNNING); - } - mutex_unlock(&mwave_mutex); - } - break; - - case IOCTL_MW_UNREGISTER_IPC: { - unsigned int ipcnum = (unsigned int) ioarg; - - if (ipcnum >= ARRAY_SIZE(pDrvData->IPCs)) { - pr_err("%s: IOCTL_MW_UNREGISTER_IPC: Error: Invalid ipcnum %x\n", - __func__, ipcnum); - return -EINVAL; - } - ipcnum = array_index_nospec(ipcnum, - ARRAY_SIZE(pDrvData->IPCs)); - mutex_lock(&mwave_mutex); - if (pDrvData->IPCs[ipcnum].bIsEnabled == true) { - pDrvData->IPCs[ipcnum].bIsEnabled = false; - if (pDrvData->IPCs[ipcnum].bIsHere == true) { - wake_up_interruptible(&pDrvData->IPCs[ipcnum].ipc_wait_queue); - } - } - mutex_unlock(&mwave_mutex); - } - break; - - default: - return -ENOTTY; - } /* switch */ - - return retval; -} - -static int register_serial_portandirq(unsigned int port, int irq) -{ - struct uart_8250_port uart; - - switch ( port ) { - case 0x3f8: - case 0x2f8: - case 0x3e8: - case 0x2e8: - /* OK */ - break; - default: - pr_err("%s: Error: Illegal port %x\n", __func__, port); - return -1; - } /* switch */ - /* port is okay */ - - switch ( irq ) { - case 3: - case 4: - case 5: - case 7: - /* OK */ - break; - default: - pr_err("%s: Error: Illegal irq %x\n", __func__, irq); - return -1; - } /* switch */ - /* irq is okay */ - - memset(&uart, 0, sizeof(uart)); - - uart.port.uartclk = 1843200; - uart.port.iobase = port; - uart.port.irq = irq; - uart.port.iotype = UPIO_PORT; - uart.port.flags = UPF_SHARE_IRQ; - return serial8250_register_8250_port(&uart); -} - -static const struct file_operations mwave_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = mwave_ioctl, - .llseek = default_llseek, -}; - -static struct miscdevice mwave_misc_dev = { MWAVE_MINOR, "mwave", &mwave_fops }; - -/* -* mwave_init is called on module load -* -* mwave_exit is called on module unload -* mwave_exit is also used to clean up after an aborted mwave_init -*/ -static void mwave_exit(void) -{ - struct mwave_device_data *pDrvData = &mwave_s_mdd; - - if ( pDrvData->sLine >= 0 ) { - serial8250_unregister_port(pDrvData->sLine); - } - if (pDrvData->bMwaveDevRegistered) { - misc_deregister(&mwave_misc_dev); - } - if (pDrvData->bDSPEnabled) { - tp3780I_DisableDSP(&pDrvData->rBDData); - } - if (pDrvData->bResourcesClaimed) { - tp3780I_ReleaseResources(&pDrvData->rBDData); - } - if (pDrvData->bBDInitialized) { - tp3780I_Cleanup(&pDrvData->rBDData); - } -} - -module_exit(mwave_exit); - -static int __init mwave_init(void) -{ - int i; - int retval = 0; - struct mwave_device_data *pDrvData = &mwave_s_mdd; - - memset(&mwave_s_mdd, 0, sizeof(mwave_s_mdd)); - - pDrvData->bBDInitialized = false; - pDrvData->bResourcesClaimed = false; - pDrvData->bDSPEnabled = false; - pDrvData->bDSPReset = false; - pDrvData->bMwaveDevRegistered = false; - pDrvData->sLine = -1; - - for (i = 0; i < ARRAY_SIZE(pDrvData->IPCs); i++) { - pDrvData->IPCs[i].bIsEnabled = false; - pDrvData->IPCs[i].bIsHere = false; - pDrvData->IPCs[i].usIntCount = 0; /* no ints received yet */ - init_waitqueue_head(&pDrvData->IPCs[i].ipc_wait_queue); - } - - retval = tp3780I_InitializeBoardData(&pDrvData->rBDData); - if (retval) { - pr_err("%s: Error: Failed to initialize board data\n", __func__); - goto cleanup_error; - } - pDrvData->bBDInitialized = true; - - retval = tp3780I_CalcResources(&pDrvData->rBDData); - if (retval) { - pr_err("%s: Error: Failed to calculate resources\n", __func__); - goto cleanup_error; - } - - retval = tp3780I_ClaimResources(&pDrvData->rBDData); - if (retval) { - pr_err("%s: Error: Failed to claim resources\n", __func__); - goto cleanup_error; - } - pDrvData->bResourcesClaimed = true; - - retval = tp3780I_EnableDSP(&pDrvData->rBDData); - if (retval) { - pr_err("%s: Error: Failed to enable DSP\n", __func__); - goto cleanup_error; - } - pDrvData->bDSPEnabled = true; - - if (misc_register(&mwave_misc_dev) < 0) { - pr_err("%s: Error: Failed to register misc device\n", __func__); - goto cleanup_error; - } - pDrvData->bMwaveDevRegistered = true; - - pDrvData->sLine = register_serial_portandirq( - pDrvData->rBDData.rDspSettings.usUartBaseIO, - pDrvData->rBDData.rDspSettings.usUartIrq - ); - if (pDrvData->sLine < 0) { - pr_err("%s: Error: Failed to register serial driver\n", __func__); - goto cleanup_error; - } - /* uart is registered */ - - /* SUCCESS! */ - return 0; - -cleanup_error: - pr_err("%s: Error: Failed to initialize\n", __func__); - mwave_exit(); /* clean up */ - - return -EIO; -} - -module_init(mwave_init); - diff --git a/drivers/char/mwave/mwavedd.h b/drivers/char/mwave/mwavedd.h deleted file mode 100644 index e1da1493eec5..000000000000 --- a/drivers/char/mwave/mwavedd.h +++ /dev/null @@ -1,90 +0,0 @@ -/* -* -* mwavedd.h -- declarations for mwave device driver -* -* -* Written By: Mike Sullivan IBM Corporation -* -* Copyright (C) 1999 IBM Corporation -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* NO WARRANTY -* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR -* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT -* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, -* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is -* solely responsible for determining the appropriateness of using and -* distributing the Program and assumes all risks associated with its -* exercise of rights under this Agreement, including but not limited to -* the risks and costs of program errors, damage to or loss of data, -* programs or equipment, and unavailability or interruption of operations. -* -* DISCLAIMER OF LIABILITY -* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -* -* -* 10/23/2000 - Alpha Release -* First release to the public -*/ - -#ifndef _LINUX_MWAVEDD_H -#define _LINUX_MWAVEDD_H -#include "3780i.h" -#include "tp3780i.h" -#include "smapi.h" -#include "mwavepub.h" -#include -#include -#include - -extern int mwave_3780i_irq; -extern int mwave_3780i_io; -extern int mwave_uart_irq; -extern int mwave_uart_io; - -struct mwave_ipc { - unsigned short usIntCount; /* 0=none, 1=first, 2=greater than 1st */ - bool bIsEnabled; - bool bIsHere; - /* entry spin lock */ - wait_queue_head_t ipc_wait_queue; -}; - -struct mwave_device_data { - struct thinkpad_bd_data rBDData; /* board driver's data area */ - unsigned long ulIPCSource_ISR; /* IPC source bits for recently processed intr, set during ISR processing */ - unsigned long ulIPCSource_DPC; /* IPC source bits for recently processed intr, set during DPC processing */ - bool bBDInitialized; - bool bResourcesClaimed; - bool bDSPEnabled; - bool bDSPReset; - struct mwave_ipc IPCs[16]; - bool bMwaveDevRegistered; - short sLine; - int nr_registered_attrs; - int device_registered; - -}; - -extern struct mwave_device_data mwave_s_mdd; - -#endif diff --git a/drivers/char/mwave/mwavepub.h b/drivers/char/mwave/mwavepub.h deleted file mode 100644 index 280327bdaa38..000000000000 --- a/drivers/char/mwave/mwavepub.h +++ /dev/null @@ -1,89 +0,0 @@ -/* -* -* mwavepub.h -- PUBLIC declarations for the mwave driver -* and applications using it -* -* -* Written By: Mike Sullivan IBM Corporation -* -* Copyright (C) 1999 IBM Corporation -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* NO WARRANTY -* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR -* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT -* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, -* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is -* solely responsible for determining the appropriateness of using and -* distributing the Program and assumes all risks associated with its -* exercise of rights under this Agreement, including but not limited to -* the risks and costs of program errors, damage to or loss of data, -* programs or equipment, and unavailability or interruption of operations. -* -* DISCLAIMER OF LIABILITY -* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -* -* -* 10/23/2000 - Alpha Release -* First release to the public -*/ - -#ifndef _LINUX_MWAVEPUB_H -#define _LINUX_MWAVEPUB_H - -#include - - -struct mw_abilities { - unsigned long instr_per_sec; - unsigned long data_size; - unsigned long inst_size; - unsigned long bus_dma_bw; - unsigned short uart_enable; - short component_count; - unsigned long component_list[7]; - char mwave_os_name[16]; - char bios_task_name[16]; -}; - - -struct mw_readwrite { - unsigned short usDspAddress; /* The dsp address */ - unsigned long ulDataLength; /* The size in bytes of the data or user buffer */ - void __user *pBuf; /* Input:variable sized buffer */ -}; - -#define IOCTL_MW_RESET _IO(MWAVE_MINOR,1) -#define IOCTL_MW_RUN _IO(MWAVE_MINOR,2) -#define IOCTL_MW_DSP_ABILITIES _IOR(MWAVE_MINOR,3,struct mw_abilities) -#define IOCTL_MW_READ_DATA _IOR(MWAVE_MINOR,4,struct mw_readwrite) -#define IOCTL_MW_READCLEAR_DATA _IOR(MWAVE_MINOR,5,struct mw_readwrite) -#define IOCTL_MW_READ_INST _IOR(MWAVE_MINOR,6,struct mw_readwrite) -#define IOCTL_MW_WRITE_DATA _IOW(MWAVE_MINOR,7,struct mw_readwrite) -#define IOCTL_MW_WRITE_INST _IOW(MWAVE_MINOR,8,struct mw_readwrite) -#define IOCTL_MW_REGISTER_IPC _IOW(MWAVE_MINOR,9,int) -#define IOCTL_MW_UNREGISTER_IPC _IOW(MWAVE_MINOR,10,int) -#define IOCTL_MW_GET_IPC _IOW(MWAVE_MINOR,11,int) -#define IOCTL_MW_TRACE _IOR(MWAVE_MINOR,12,struct mw_readwrite) - - -#endif diff --git a/drivers/char/mwave/smapi.c b/drivers/char/mwave/smapi.c deleted file mode 100644 index df6354b24339..000000000000 --- a/drivers/char/mwave/smapi.c +++ /dev/null @@ -1,404 +0,0 @@ -/* -* -* smapi.c -- SMAPI interface routines -* -* -* Written By: Mike Sullivan IBM Corporation -* -* Copyright (C) 1999 IBM Corporation -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* NO WARRANTY -* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR -* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT -* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, -* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is -* solely responsible for determining the appropriateness of using and -* distributing the Program and assumes all risks associated with its -* exercise of rights under this Agreement, including but not limited to -* the risks and costs of program errors, damage to or loss of data, -* programs or equipment, and unavailability or interruption of operations. -* -* DISCLAIMER OF LIABILITY -* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -* -* -* 10/23/2000 - Alpha Release -* First release to the public -*/ - -#define pr_fmt(fmt) "smapi: " fmt - -#include -#include /* CMOS defines */ -#include "smapi.h" -#include "mwavedd.h" - -static unsigned short g_usSmapiPort = 0; - - -static int smapi_request(unsigned short inBX, unsigned short inCX, - unsigned short inDI, unsigned short inSI, - unsigned short *outAX, unsigned short *outBX, - unsigned short *outCX, unsigned short *outDX, - unsigned short *outDI, unsigned short *outSI) -{ - unsigned short myoutAX = 2, *pmyoutAX = &myoutAX; - unsigned short myoutBX = 3, *pmyoutBX = &myoutBX; - unsigned short myoutCX = 4, *pmyoutCX = &myoutCX; - unsigned short myoutDX = 5, *pmyoutDX = &myoutDX; - unsigned short myoutDI = 6, *pmyoutDI = &myoutDI; - unsigned short myoutSI = 7, *pmyoutSI = &myoutSI; - unsigned short usSmapiOK = -EIO, *pusSmapiOK = &usSmapiOK; - unsigned int inBXCX = (inBX << 16) | inCX; - unsigned int inDISI = (inDI << 16) | inSI; - - __asm__ __volatile__("movw $0x5380,%%ax\n\t" - "movl %7,%%ebx\n\t" - "shrl $16, %%ebx\n\t" - "movw %7,%%cx\n\t" - "movl %8,%%edi\n\t" - "shrl $16,%%edi\n\t" - "movw %8,%%si\n\t" - "movw %9,%%dx\n\t" - "out %%al,%%dx\n\t" - "out %%al,$0x4F\n\t" - "cmpb $0x53,%%ah\n\t" - "je 2f\n\t" - "1:\n\t" - "orb %%ah,%%ah\n\t" - "jnz 2f\n\t" - "movw %%ax,%0\n\t" - "movw %%bx,%1\n\t" - "movw %%cx,%2\n\t" - "movw %%dx,%3\n\t" - "movw %%di,%4\n\t" - "movw %%si,%5\n\t" - "movw $1,%6\n\t" - "2:\n\t":"=m"(*(unsigned short *) pmyoutAX), - "=m"(*(unsigned short *) pmyoutBX), - "=m"(*(unsigned short *) pmyoutCX), - "=m"(*(unsigned short *) pmyoutDX), - "=m"(*(unsigned short *) pmyoutDI), - "=m"(*(unsigned short *) pmyoutSI), - "=m"(*(unsigned short *) pusSmapiOK) - :"m"(inBXCX), "m"(inDISI), "m"(g_usSmapiPort) - :"%eax", "%ebx", "%ecx", "%edx", "%edi", - "%esi"); - - *outAX = myoutAX; - *outBX = myoutBX; - *outCX = myoutCX; - *outDX = myoutDX; - *outDI = myoutDI; - *outSI = myoutSI; - - return usSmapiOK == 1 ? 0 : -EIO; -} - - -int smapi_query_DSP_cfg(struct smapi_dsp_settings *pSettings) -{ - int bRC; - unsigned short usAX, usBX, usCX, usDX, usDI, usSI; - static const unsigned short ausDspBases[] = { - 0x0030, 0x4E30, 0x8E30, 0xCE30, - 0x0130, 0x0350, 0x0070, 0x0DB0 }; - static const unsigned short ausUartBases[] = { - 0x03F8, 0x02F8, 0x03E8, 0x02E8 }; - - bRC = smapi_request(0x1802, 0x0000, 0, 0, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) { - pr_err("%s: Error: Could not get DSP Settings. Aborting.\n", __func__); - return bRC; - } - - pSettings->bDSPPresent = ((usBX & 0x0100) != 0); - pSettings->bDSPEnabled = ((usCX & 0x0001) != 0); - pSettings->usDspIRQ = usSI & 0x00FF; - pSettings->usDspDMA = (usSI & 0xFF00) >> 8; - if ((usDI & 0x00FF) < ARRAY_SIZE(ausDspBases)) { - pSettings->usDspBaseIO = ausDspBases[usDI & 0x00FF]; - } else { - pSettings->usDspBaseIO = 0; - } - - /* check for illegal values */ - if ( pSettings->usDspBaseIO == 0 ) - pr_err("%s: Worry: DSP base I/O address is 0\n", __func__); - if ( pSettings->usDspIRQ == 0 ) - pr_err("%s: Worry: DSP IRQ line is 0\n", __func__); - - bRC = smapi_request(0x1804, 0x0000, 0, 0, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) { - pr_err("%s: Error: Could not get DSP modem settings. Aborting.\n", __func__); - return bRC; - } - - pSettings->bModemEnabled = ((usCX & 0x0001) != 0); - pSettings->usUartIRQ = usSI & 0x000F; - if (((usSI & 0xFF00) >> 8) < ARRAY_SIZE(ausUartBases)) { - pSettings->usUartBaseIO = ausUartBases[(usSI & 0xFF00) >> 8]; - } else { - pSettings->usUartBaseIO = 0; - } - - /* check for illegal values */ - if ( pSettings->usUartBaseIO == 0 ) - pr_err("%s: Worry: UART base I/O address is 0\n", __func__); - if ( pSettings->usUartIRQ == 0 ) - pr_err("%s: Worry: UART IRQ line is 0\n", __func__); - - return bRC; -} - - -int smapi_set_DSP_cfg(void) -{ - int bRC = -EIO; - int i; - unsigned short usAX, usBX, usCX, usDX, usDI, usSI; - static const unsigned short ausDspBases[] = { - 0x0030, 0x4E30, 0x8E30, 0xCE30, - 0x0130, 0x0350, 0x0070, 0x0DB0 }; - static const unsigned short ausUartBases[] = { - 0x03F8, 0x02F8, 0x03E8, 0x02E8 }; - static const unsigned short ausDspIrqs[] = { - 5, 7, 10, 11, 15 }; - static const unsigned short ausUartIrqs[] = { - 3, 4 }; - - unsigned short dspio_index = 0, uartio_index = 0; - - if (mwave_3780i_io) { - for (i = 0; i < ARRAY_SIZE(ausDspBases); i++) { - if (mwave_3780i_io == ausDspBases[i]) - break; - } - if (i == ARRAY_SIZE(ausDspBases)) { - pr_err("%s: Error: Invalid mwave_3780i_io address %x. Aborting.\n", - __func__, mwave_3780i_io); - return bRC; - } - dspio_index = i; - } - - if (mwave_3780i_irq) { - for (i = 0; i < ARRAY_SIZE(ausDspIrqs); i++) { - if (mwave_3780i_irq == ausDspIrqs[i]) - break; - } - if (i == ARRAY_SIZE(ausDspIrqs)) { - pr_err("%s: Error: Invalid mwave_3780i_irq %x. Aborting.\n", __func__, - mwave_3780i_irq); - return bRC; - } - } - - if (mwave_uart_io) { - for (i = 0; i < ARRAY_SIZE(ausUartBases); i++) { - if (mwave_uart_io == ausUartBases[i]) - break; - } - if (i == ARRAY_SIZE(ausUartBases)) { - pr_err("%s: Error: Invalid mwave_uart_io address %x. Aborting.\n", __func__, - mwave_uart_io); - return bRC; - } - uartio_index = i; - } - - - if (mwave_uart_irq) { - for (i = 0; i < ARRAY_SIZE(ausUartIrqs); i++) { - if (mwave_uart_irq == ausUartIrqs[i]) - break; - } - if (i == ARRAY_SIZE(ausUartIrqs)) { - pr_err("%s: Error: Invalid mwave_uart_irq %x. Aborting.\n", __func__, - mwave_uart_irq); - return bRC; - } - } - - if (mwave_uart_irq || mwave_uart_io) { - - /* Check serial port A */ - bRC = smapi_request(0x1402, 0x0000, 0, 0, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) goto exit_smapi_request_error; - /* bRC == 0 */ - if (usBX & 0x0100) { /* serial port A is present */ - if (usCX & 1) { /* serial port is enabled */ - if ((usSI & 0xFF) == mwave_uart_irq) { - pr_err("%s: Serial port A irq %x conflicts with mwave_uart_irq %x\n", - __func__, usSI & 0xFF, mwave_uart_irq); - goto exit_conflict; - } else { - if ((usSI >> 8) == uartio_index) { - pr_err("%s: Serial port A base I/O address %x conflicts with mwave uart I/O %x\n", - __func__, ausUartBases[usSI >> 8], - ausUartBases[uartio_index]); - goto exit_conflict; - } - } - } - } - - /* Check serial port B */ - bRC = smapi_request(0x1404, 0x0000, 0, 0, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) goto exit_smapi_request_error; - /* bRC == 0 */ - if (usBX & 0x0100) { /* serial port B is present */ - if (usCX & 1) { /* serial port is enabled */ - if ((usSI & 0xFF) == mwave_uart_irq) { - pr_err("%s: Serial port B irq %x conflicts with mwave_uart_irq %x\n", - __func__, usSI & 0xFF, mwave_uart_irq); - goto exit_conflict; - } else { - if ((usSI >> 8) == uartio_index) { - pr_err("%s: Serial port B base I/O address %x conflicts with mwave uart I/O %x\n", - __func__, ausUartBases[usSI >> 8], - ausUartBases[uartio_index]); - goto exit_conflict; - } - } - } - } - - /* Check IR port */ - bRC = smapi_request(0x1700, 0x0000, 0, 0, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) goto exit_smapi_request_error; - bRC = smapi_request(0x1704, 0x0000, 0, 0, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) goto exit_smapi_request_error; - /* bRC == 0 */ - if ((usCX & 0xff) != 0xff) { /* IR port not disabled */ - if ((usCX & 0xff) == mwave_uart_irq) { - pr_err("%s: IR port irq %x conflicts with mwave_uart_irq %x\n", - __func__, usCX & 0xff, mwave_uart_irq); - goto exit_conflict; - } else { - if ((usSI & 0xff) == uartio_index) { - pr_err("%s: IR port base I/O address %x conflicts with mwave uart I/O %x\n", - __func__, ausUartBases[usSI & 0xff], - ausUartBases[uartio_index]); - goto exit_conflict; - } - } - } - } - - bRC = smapi_request(0x1802, 0x0000, 0, 0, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) goto exit_smapi_request_error; - - if (mwave_3780i_io) { - usDI = dspio_index; - } - if (mwave_3780i_irq) { - usSI = (usSI & 0xff00) | mwave_3780i_irq; - } - - bRC = smapi_request(0x1803, 0x0101, usDI, usSI, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) goto exit_smapi_request_error; - - bRC = smapi_request(0x1804, 0x0000, 0, 0, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) goto exit_smapi_request_error; - - if (mwave_uart_io) { - usSI = (usSI & 0x00ff) | (uartio_index << 8); - } - if (mwave_uart_irq) { - usSI = (usSI & 0xff00) | mwave_uart_irq; - } - bRC = smapi_request(0x1805, 0x0101, 0, usSI, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) goto exit_smapi_request_error; - - bRC = smapi_request(0x1802, 0x0000, 0, 0, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) goto exit_smapi_request_error; - - bRC = smapi_request(0x1804, 0x0000, 0, 0, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) goto exit_smapi_request_error; - -/* normal exit: */ - return 0; - -exit_conflict: - /* Message has already been printed */ - return -EIO; - -exit_smapi_request_error: - pr_err("%s: exit on smapi_request error bRC %x\n", __func__, bRC); - return bRC; -} - - -int smapi_set_DSP_power_state(bool bOn) -{ - unsigned short usAX, usBX, usCX, usDX, usDI, usSI; - unsigned short usPowerFunction; - - usPowerFunction = (bOn) ? 1 : 0; - - return smapi_request(0x4901, 0x0000, 0, usPowerFunction, &usAX, &usBX, &usCX, &usDX, &usDI, - &usSI); -} - -int smapi_init(void) -{ - int retval = -EIO; - unsigned short usSmapiID = 0; - unsigned long flags; - - spin_lock_irqsave(&rtc_lock, flags); - usSmapiID = CMOS_READ(0x7C); - usSmapiID |= (CMOS_READ(0x7D) << 8); - spin_unlock_irqrestore(&rtc_lock, flags); - - if (usSmapiID == 0x5349) { - spin_lock_irqsave(&rtc_lock, flags); - g_usSmapiPort = CMOS_READ(0x7E); - g_usSmapiPort |= (CMOS_READ(0x7F) << 8); - spin_unlock_irqrestore(&rtc_lock, flags); - if (g_usSmapiPort == 0) { - pr_err("%s: ERROR unable to read from SMAPI port\n", __func__); - } else { - retval = 0; - //SmapiQuerySystemID(); - } - } else { - pr_err("%s: ERROR invalid usSmapiID\n", __func__); - retval = -ENXIO; - } - - return retval; -} diff --git a/drivers/char/mwave/smapi.h b/drivers/char/mwave/smapi.h deleted file mode 100644 index e605b16ed23c..000000000000 --- a/drivers/char/mwave/smapi.h +++ /dev/null @@ -1,76 +0,0 @@ -/* -* -* smapi.h -- declarations for SMAPI interface routines -* -* -* Written By: Mike Sullivan IBM Corporation -* -* Copyright (C) 1999 IBM Corporation -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* NO WARRANTY -* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR -* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT -* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, -* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is -* solely responsible for determining the appropriateness of using and -* distributing the Program and assumes all risks associated with its -* exercise of rights under this Agreement, including but not limited to -* the risks and costs of program errors, damage to or loss of data, -* programs or equipment, and unavailability or interruption of operations. -* -* DISCLAIMER OF LIABILITY -* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -* -* -* 10/23/2000 - Alpha Release -* First release to the public -*/ - -#ifndef _LINUX_SMAPI_H -#define _LINUX_SMAPI_H - -struct smapi_dsp_settings { - int bDSPPresent; - int bDSPEnabled; - int bModemEnabled; - int bMIDIEnabled; - int bSblstEnabled; - unsigned short usDspIRQ; - unsigned short usDspDMA; - unsigned short usDspBaseIO; - unsigned short usUartIRQ; - unsigned short usUartBaseIO; - unsigned short usMidiIRQ; - unsigned short usMidiBaseIO; - unsigned short usSndblstIRQ; - unsigned short usSndblstDMA; - unsigned short usSndblstBaseIO; -}; - -int smapi_init(void); -int smapi_query_DSP_cfg(struct smapi_dsp_settings *pSettings); -int smapi_set_DSP_cfg(void); -int smapi_set_DSP_power_state(bool bOn); - - -#endif diff --git a/drivers/char/mwave/tp3780i.c b/drivers/char/mwave/tp3780i.c deleted file mode 100644 index 7363b0f764e0..000000000000 --- a/drivers/char/mwave/tp3780i.c +++ /dev/null @@ -1,477 +0,0 @@ -/* -* -* tp3780i.c -- board driver for 3780i on ThinkPads -* -* -* Written By: Mike Sullivan IBM Corporation -* -* Copyright (C) 1999 IBM Corporation -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* NO WARRANTY -* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR -* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT -* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, -* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is -* solely responsible for determining the appropriateness of using and -* distributing the Program and assumes all risks associated with its -* exercise of rights under this Agreement, including but not limited to -* the risks and costs of program errors, damage to or loss of data, -* programs or equipment, and unavailability or interruption of operations. -* -* DISCLAIMER OF LIABILITY -* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -* -* -* 10/23/2000 - Alpha Release -* First release to the public -*/ - -#define pr_fmt(fmt) "tp3780i: " fmt - -#include -#include -#include -#include -#include -#include "smapi.h" -#include "mwavedd.h" -#include "tp3780i.h" -#include "3780i.h" -#include "mwavepub.h" - -static unsigned short s_ausThinkpadIrqToField[16] = - { 0xFFFF, 0xFFFF, 0xFFFF, 0x0001, 0x0002, 0x0003, 0xFFFF, 0x0004, - 0xFFFF, 0xFFFF, 0x0005, 0x0006, 0xFFFF, 0xFFFF, 0xFFFF, 0x0007 }; -static unsigned short s_ausThinkpadDmaToField[8] = - { 0x0001, 0x0002, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0003, 0x0004 }; -static unsigned short s_numIrqs = 16, s_numDmas = 8; - - -static void EnableSRAM(struct thinkpad_bd_data *pBDData) -{ - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - unsigned short usDspBaseIO = pSettings->usDspBaseIO; - DSP_GPIO_OUTPUT_DATA_15_8 rGpioOutputData; - DSP_GPIO_DRIVER_ENABLE_15_8 rGpioDriverEnable; - DSP_GPIO_MODE_15_8 rGpioMode; - - MKWORD(rGpioMode) = ReadMsaCfg(DSP_GpioModeControl_15_8); - rGpioMode.GpioMode10 = 0; - WriteMsaCfg(DSP_GpioModeControl_15_8, MKWORD(rGpioMode)); - - MKWORD(rGpioDriverEnable) = 0; - rGpioDriverEnable.Enable10 = true; - rGpioDriverEnable.Mask10 = true; - WriteMsaCfg(DSP_GpioDriverEnable_15_8, MKWORD(rGpioDriverEnable)); - - MKWORD(rGpioOutputData) = 0; - rGpioOutputData.Latch10 = 0; - rGpioOutputData.Mask10 = true; - WriteMsaCfg(DSP_GpioOutputData_15_8, MKWORD(rGpioOutputData)); -} - - -static irqreturn_t UartInterrupt(int irq, void *dev_id) -{ - return IRQ_HANDLED; -} - -static irqreturn_t DspInterrupt(int irq, void *dev_id) -{ - struct mwave_device_data *pDrvData = &mwave_s_mdd; - struct dsp_3780i_config_settings *pSettings = &pDrvData->rBDData.rDspSettings; - unsigned short usDspBaseIO = pSettings->usDspBaseIO; - unsigned short usIPCSource = 0, usIsolationMask, usPCNum; - - if (dsp3780I_GetIPCSource(usDspBaseIO, &usIPCSource) == 0) { - usIsolationMask = 1; - for (usPCNum = 1; usPCNum <= 16; usPCNum++) { - if (usIPCSource & usIsolationMask) { - usIPCSource &= ~usIsolationMask; - if (pDrvData->IPCs[usPCNum - 1].usIntCount == 0) { - pDrvData->IPCs[usPCNum - 1].usIntCount = 1; - } - if (pDrvData->IPCs[usPCNum - 1].bIsEnabled == true) { - wake_up_interruptible(&pDrvData->IPCs[usPCNum - 1].ipc_wait_queue); - } - } - if (usIPCSource == 0) - break; - /* try next IPC */ - usIsolationMask = usIsolationMask << 1; - } - } - return IRQ_HANDLED; -} - - -int tp3780I_InitializeBoardData(struct thinkpad_bd_data *pBDData) -{ - int retval = 0; - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - - pBDData->bDSPEnabled = false; - pSettings->bInterruptClaimed = false; - - retval = smapi_init(); - if (retval) { - pr_err("%s: Error: SMAPI is not available on this machine\n", __func__); - } else { - if (mwave_3780i_irq || mwave_3780i_io || mwave_uart_irq || mwave_uart_io) { - retval = smapi_set_DSP_cfg(); - } - } - - return retval; -} - -void tp3780I_Cleanup(struct thinkpad_bd_data *pBDData) -{ -} - -int tp3780I_CalcResources(struct thinkpad_bd_data *pBDData) -{ - struct smapi_dsp_settings rSmapiInfo; - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - - if (smapi_query_DSP_cfg(&rSmapiInfo)) { - pr_err("%s: Error: Could not query DSP config. Aborting.\n", __func__); - return -EIO; - } - - /* Sanity check */ - if ( - ( rSmapiInfo.usDspIRQ == 0 ) - || ( rSmapiInfo.usDspBaseIO == 0 ) - || ( rSmapiInfo.usUartIRQ == 0 ) - || ( rSmapiInfo.usUartBaseIO == 0 ) - ) { - pr_err("%s: Error: Illegal resource setting. Aborting.\n", __func__); - return -EIO; - } - - pSettings->bDSPEnabled = (rSmapiInfo.bDSPEnabled && rSmapiInfo.bDSPPresent); - pSettings->bModemEnabled = rSmapiInfo.bModemEnabled; - pSettings->usDspIrq = rSmapiInfo.usDspIRQ; - pSettings->usDspDma = rSmapiInfo.usDspDMA; - pSettings->usDspBaseIO = rSmapiInfo.usDspBaseIO; - pSettings->usUartIrq = rSmapiInfo.usUartIRQ; - pSettings->usUartBaseIO = rSmapiInfo.usUartBaseIO; - - pSettings->uDStoreSize = TP_ABILITIES_DATA_SIZE; - pSettings->uIStoreSize = TP_ABILITIES_INST_SIZE; - pSettings->uIps = TP_ABILITIES_INTS_PER_SEC; - - if (pSettings->bDSPEnabled && pSettings->bModemEnabled && pSettings->usDspIrq == pSettings->usUartIrq) { - pBDData->bShareDspIrq = pBDData->bShareUartIrq = 1; - } else { - pBDData->bShareDspIrq = pBDData->bShareUartIrq = 0; - } - - return 0; -} - - -int tp3780I_ClaimResources(struct thinkpad_bd_data *pBDData) -{ - int retval = 0; - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - struct resource *pres; - - pres = request_region(pSettings->usDspBaseIO, 16, "mwave_3780i"); - if ( pres == NULL ) retval = -EIO; - - if (retval) { - pr_err("%s: Error: Could not claim I/O region starting at %x\n", __func__, - pSettings->usDspBaseIO); - return -EIO; - } - - return retval; -} - -int tp3780I_ReleaseResources(struct thinkpad_bd_data *pBDData) -{ - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - - release_region(pSettings->usDspBaseIO & (~3), 16); - - if (pSettings->bInterruptClaimed) { - free_irq(pSettings->usDspIrq, NULL); - pSettings->bInterruptClaimed = false; - } - - return 0; -} - - - -int tp3780I_EnableDSP(struct thinkpad_bd_data *pBDData) -{ - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - bool bDSPPoweredUp = false, bInterruptAllocated = false; - - if (pBDData->bDSPEnabled) { - pr_err("%s: Error: DSP already enabled!\n", __func__); - goto exit_cleanup; - } - - if (!pSettings->bDSPEnabled) { - pr_err("%s: Error: pSettings->bDSPEnabled not set\n", __func__); - goto exit_cleanup; - } - - if ( - (pSettings->usDspIrq >= s_numIrqs) - || (pSettings->usDspDma >= s_numDmas) - || (s_ausThinkpadIrqToField[pSettings->usDspIrq] == 0xFFFF) - || (s_ausThinkpadDmaToField[pSettings->usDspDma] == 0xFFFF) - ) { - pr_err("%s: Error: invalid irq %x\n", __func__, pSettings->usDspIrq); - goto exit_cleanup; - } - - if ( - ((pSettings->usDspBaseIO & 0xF00F) != 0) - || (pSettings->usDspBaseIO & 0x0FF0) == 0 - ) { - pr_err("%s: Error: Invalid DSP base I/O address %x\n", __func__, - pSettings->usDspBaseIO); - goto exit_cleanup; - } - - if (pSettings->bModemEnabled) { - if ( - pSettings->usUartIrq >= s_numIrqs - || s_ausThinkpadIrqToField[pSettings->usUartIrq] == 0xFFFF - ) { - pr_err("%s: Error: Invalid UART IRQ %x\n", __func__, pSettings->usUartIrq); - goto exit_cleanup; - } - switch (pSettings->usUartBaseIO) { - case 0x03F8: - case 0x02F8: - case 0x03E8: - case 0x02E8: - break; - - default: - pr_err("%s: Error: Invalid UART base I/O address %x\n", __func__, - pSettings->usUartBaseIO); - goto exit_cleanup; - } - } - - pSettings->bDspIrqActiveLow = pSettings->bDspIrqPulse = true; - pSettings->bUartIrqActiveLow = pSettings->bUartIrqPulse = true; - - if (pBDData->bShareDspIrq) { - pSettings->bDspIrqActiveLow = false; - } - if (pBDData->bShareUartIrq) { - pSettings->bUartIrqActiveLow = false; - } - - pSettings->usNumTransfers = TP_CFG_NumTransfers; - pSettings->usReRequest = TP_CFG_RerequestTimer; - pSettings->bEnableMEMCS16 = TP_CFG_MEMCS16; - pSettings->usIsaMemCmdWidth = TP_CFG_IsaMemCmdWidth; - pSettings->bGateIOCHRDY = TP_CFG_GateIOCHRDY; - pSettings->bEnablePwrMgmt = TP_CFG_EnablePwrMgmt; - pSettings->usHBusTimerLoadValue = TP_CFG_HBusTimerValue; - pSettings->bDisableLBusTimeout = TP_CFG_DisableLBusTimeout; - pSettings->usN_Divisor = TP_CFG_N_Divisor; - pSettings->usM_Multiplier = TP_CFG_M_Multiplier; - pSettings->bPllBypass = TP_CFG_PllBypass; - pSettings->usChipletEnable = TP_CFG_ChipletEnable; - - if (request_irq(pSettings->usUartIrq, &UartInterrupt, 0, "mwave_uart", NULL)) { - pr_err("%s: Error: Could not get UART IRQ %x\n", __func__, pSettings->usUartIrq); - goto exit_cleanup; - } else { /* no conflict just release */ - free_irq(pSettings->usUartIrq, NULL); - } - - if (request_irq(pSettings->usDspIrq, &DspInterrupt, 0, "mwave_3780i", NULL)) { - pr_err("%s: Error: Could not get 3780i IRQ %x\n", __func__, pSettings->usDspIrq); - goto exit_cleanup; - } else { - bInterruptAllocated = true; - pSettings->bInterruptClaimed = true; - } - - smapi_set_DSP_power_state(false); - if (smapi_set_DSP_power_state(true)) { - pr_err("%s: Error: smapi_set_DSP_power_state(true) failed\n", __func__); - goto exit_cleanup; - } else { - bDSPPoweredUp = true; - } - - if (dsp3780I_EnableDSP(pSettings, s_ausThinkpadIrqToField, s_ausThinkpadDmaToField)) { - pr_err("%s: Error: dsp7880I_EnableDSP() failed\n", __func__); - goto exit_cleanup; - } - - EnableSRAM(pBDData); - - pBDData->bDSPEnabled = true; - - return 0; - -exit_cleanup: - pr_err("%s: Cleaning up\n", __func__); - if (bDSPPoweredUp) - smapi_set_DSP_power_state(false); - if (bInterruptAllocated) { - free_irq(pSettings->usDspIrq, NULL); - pSettings->bInterruptClaimed = false; - } - return -EIO; -} - - -int tp3780I_DisableDSP(struct thinkpad_bd_data *pBDData) -{ - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - - if (pBDData->bDSPEnabled) { - dsp3780I_DisableDSP(&pBDData->rDspSettings); - if (pSettings->bInterruptClaimed) { - free_irq(pSettings->usDspIrq, NULL); - pSettings->bInterruptClaimed = false; - } - smapi_set_DSP_power_state(false); - pBDData->bDSPEnabled = false; - } - - return 0; -} - - -int tp3780I_ResetDSP(struct thinkpad_bd_data *pBDData) -{ - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - - if (dsp3780I_Reset(pSettings) == 0) { - EnableSRAM(pBDData); - return 0; - } - return -EIO; -} - - -int tp3780I_StartDSP(struct thinkpad_bd_data *pBDData) -{ - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - - if (dsp3780I_Run(pSettings) == 0) { - // @BUG @TBD EnableSRAM(pBDData); - } else { - return -EIO; - } - - return 0; -} - - -int tp3780I_QueryAbilities(struct thinkpad_bd_data *pBDData, struct mw_abilities *pAbilities) -{ - memset(pAbilities, 0, sizeof(*pAbilities)); - /* fill out standard constant fields */ - pAbilities->instr_per_sec = pBDData->rDspSettings.uIps; - pAbilities->data_size = pBDData->rDspSettings.uDStoreSize; - pAbilities->inst_size = pBDData->rDspSettings.uIStoreSize; - pAbilities->bus_dma_bw = pBDData->rDspSettings.uDmaBandwidth; - - /* fill out dynamically determined fields */ - pAbilities->component_list[0] = 0x00010000 | MW_ADC_MASK; - pAbilities->component_list[1] = 0x00010000 | MW_ACI_MASK; - pAbilities->component_list[2] = 0x00010000 | MW_AIC1_MASK; - pAbilities->component_list[3] = 0x00010000 | MW_AIC2_MASK; - pAbilities->component_list[4] = 0x00010000 | MW_CDDAC_MASK; - pAbilities->component_list[5] = 0x00010000 | MW_MIDI_MASK; - pAbilities->component_list[6] = 0x00010000 | MW_UART_MASK; - pAbilities->component_count = 7; - - /* Fill out Mwave OS and BIOS task names */ - - memcpy(pAbilities->mwave_os_name, TP_ABILITIES_MWAVEOS_NAME, - sizeof(TP_ABILITIES_MWAVEOS_NAME)); - memcpy(pAbilities->bios_task_name, TP_ABILITIES_BIOSTASK_NAME, - sizeof(TP_ABILITIES_BIOSTASK_NAME)); - - return 0; -} - -int tp3780I_ReadWriteDspDStore(struct thinkpad_bd_data *pBDData, unsigned int uOpcode, - void __user *pvBuffer, unsigned int uCount, - unsigned long ulDSPAddr) -{ - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - unsigned short usDspBaseIO = pSettings->usDspBaseIO; - bool bRC = 0; - - if (pBDData->bDSPEnabled) { - switch (uOpcode) { - case IOCTL_MW_READ_DATA: - bRC = dsp3780I_ReadDStore(usDspBaseIO, pvBuffer, uCount, ulDSPAddr); - break; - - case IOCTL_MW_READCLEAR_DATA: - bRC = dsp3780I_ReadAndClearDStore(usDspBaseIO, pvBuffer, uCount, ulDSPAddr); - break; - - case IOCTL_MW_WRITE_DATA: - bRC = dsp3780I_WriteDStore(usDspBaseIO, pvBuffer, uCount, ulDSPAddr); - break; - } - } - - return bRC ? -EIO : 0; -} - - -int tp3780I_ReadWriteDspIStore(struct thinkpad_bd_data *pBDData, unsigned int uOpcode, - void __user *pvBuffer, unsigned int uCount, - unsigned long ulDSPAddr) -{ - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - unsigned short usDspBaseIO = pSettings->usDspBaseIO; - bool bRC = 0; - - if (pBDData->bDSPEnabled) { - switch (uOpcode) { - case IOCTL_MW_READ_INST: - bRC = dsp3780I_ReadIStore(usDspBaseIO, pvBuffer, uCount, ulDSPAddr); - break; - - case IOCTL_MW_WRITE_INST: - bRC = dsp3780I_WriteIStore(usDspBaseIO, pvBuffer, uCount, ulDSPAddr); - break; - } - } - - return bRC ? -EIO : 0; -} - diff --git a/drivers/char/mwave/tp3780i.h b/drivers/char/mwave/tp3780i.h deleted file mode 100644 index c0001a344741..000000000000 --- a/drivers/char/mwave/tp3780i.h +++ /dev/null @@ -1,103 +0,0 @@ -/* -* -* tp3780i.h -- declarations for tp3780i.c -* -* -* Written By: Mike Sullivan IBM Corporation -* -* Copyright (C) 1999 IBM Corporation -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* NO WARRANTY -* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR -* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT -* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, -* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is -* solely responsible for determining the appropriateness of using and -* distributing the Program and assumes all risks associated with its -* exercise of rights under this Agreement, including but not limited to -* the risks and costs of program errors, damage to or loss of data, -* programs or equipment, and unavailability or interruption of operations. -* -* DISCLAIMER OF LIABILITY -* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -* -* -* 10/23/2000 - Alpha Release -* First release to the public -*/ - -#ifndef _LINUX_TP3780I_H -#define _LINUX_TP3780I_H - -#include -#include "mwavepub.h" - - -/* DSP abilities constants for 3780i based Thinkpads */ -#define TP_ABILITIES_INTS_PER_SEC 39160800 -#define TP_ABILITIES_DATA_SIZE 32768 -#define TP_ABILITIES_INST_SIZE 32768 -#define TP_ABILITIES_MWAVEOS_NAME "mwaveos0700.dsp" -#define TP_ABILITIES_BIOSTASK_NAME "mwbio701.dsp" - - -/* DSP configuration values for 3780i based Thinkpads */ -#define TP_CFG_NumTransfers 3 /* 16 transfers */ -#define TP_CFG_RerequestTimer 1 /* 2 usec */ -#define TP_CFG_MEMCS16 0 /* Disabled, 16-bit memory assumed */ -#define TP_CFG_IsaMemCmdWidth 3 /* 295 nsec (16-bit) */ -#define TP_CFG_GateIOCHRDY 0 /* No IOCHRDY gating */ -#define TP_CFG_EnablePwrMgmt 1 /* Enable low poser suspend/resume */ -#define TP_CFG_HBusTimerValue 255 /* HBus timer load value */ -#define TP_CFG_DisableLBusTimeout 0 /* Enable LBus timeout */ -#define TP_CFG_N_Divisor 32 /* Clock = 39.1608 Mhz */ -#define TP_CFG_M_Multiplier 37 /* " */ -#define TP_CFG_PllBypass 0 /* don't bypass */ -#define TP_CFG_ChipletEnable 0xFFFF /* Enable all chiplets */ - -struct thinkpad_bd_data { - int bDSPEnabled; - int bShareDspIrq; - int bShareUartIrq; - struct dsp_3780i_config_settings rDspSettings; -}; - -int tp3780I_InitializeBoardData(struct thinkpad_bd_data *pBDData); -int tp3780I_CalcResources(struct thinkpad_bd_data *pBDData); -int tp3780I_ClaimResources(struct thinkpad_bd_data *pBDData); -int tp3780I_ReleaseResources(struct thinkpad_bd_data *pBDData); -int tp3780I_EnableDSP(struct thinkpad_bd_data *pBDData); -int tp3780I_DisableDSP(struct thinkpad_bd_data *pBDData); -int tp3780I_ResetDSP(struct thinkpad_bd_data *pBDData); -int tp3780I_StartDSP(struct thinkpad_bd_data *pBDData); -int tp3780I_QueryAbilities(struct thinkpad_bd_data *pBDData, struct mw_abilities *pAbilities); -void tp3780I_Cleanup(struct thinkpad_bd_data *pBDData); -int tp3780I_ReadWriteDspDStore(struct thinkpad_bd_data *pBDData, unsigned int uOpcode, - void __user *pvBuffer, unsigned int uCount, - unsigned long ulDSPAddr); -int tp3780I_ReadWriteDspIStore(struct thinkpad_bd_data *pBDData, unsigned int uOpcode, - void __user *pvBuffer, unsigned int uCount, - unsigned long ulDSPAddr); - - -#endif diff --git a/include/linux/miscdevice.h b/include/linux/miscdevice.h index 7d0aa718499c..fa9000f68523 100644 --- a/include/linux/miscdevice.h +++ b/include/linux/miscdevice.h @@ -52,7 +52,6 @@ #define PXA3XX_GCU_MINOR 197 #define TUN_MINOR 200 #define CUSE_MINOR 203 -#define MWAVE_MINOR 219 /* ACP/Mwave Modem */ #define MPT_MINOR 220 #define MPT2SAS_MINOR 221 #define MPT3SAS_MINOR 222 From b54c82d6cbfc76647ba558e8e3647eb2b0ba0e2b Mon Sep 17 00:00:00 2001 From: Markus Perkins Date: Tue, 2 Dec 2025 11:48:24 +0100 Subject: [PATCH 078/297] misc: eeprom: Fix EWEN/EWDS/ERAL commands for 93xx56 and 93xx66 commit 14374fbb3f06 ("misc: eeprom_93xx46: Add new 93c56 and 93c66 compatible strings") added support for 93xx56 and 93xx66 eeproms, but didn't take into account that the write enable/disable + erase all commands are hardcoded for the 6-bit address of the 93xx46. This commit fixes the command word generation by increasing the number of shifts as the address field grows, keeping the command intact. Also, the check for 8-bit or 16-bit mode is no longer required as this is already taken into account in the edev->addrlen field. Signed-off-by: Markus Perkins Link: https://patch.msgid.link/20251202104823.429869-3-markus@notsyncing.net Signed-off-by: Greg Kroah-Hartman --- drivers/misc/eeprom/eeprom_93xx46.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/misc/eeprom/eeprom_93xx46.c b/drivers/misc/eeprom/eeprom_93xx46.c index 9cae6f530679..5230e910a1d1 100644 --- a/drivers/misc/eeprom/eeprom_93xx46.c +++ b/drivers/misc/eeprom/eeprom_93xx46.c @@ -45,6 +45,7 @@ struct eeprom_93xx46_platform_data { #define OP_START 0x4 #define OP_WRITE (OP_START | 0x1) #define OP_READ (OP_START | 0x2) +/* The following addresses are offset for the 1K EEPROM variant in 16-bit mode */ #define ADDR_EWDS 0x00 #define ADDR_ERAL 0x20 #define ADDR_EWEN 0x30 @@ -191,10 +192,7 @@ static int eeprom_93xx46_ew(struct eeprom_93xx46_dev *edev, int is_on) bits = edev->addrlen + 3; cmd_addr = OP_START << edev->addrlen; - if (edev->pdata->flags & EE_ADDR8) - cmd_addr |= (is_on ? ADDR_EWEN : ADDR_EWDS) << 1; - else - cmd_addr |= (is_on ? ADDR_EWEN : ADDR_EWDS); + cmd_addr |= (is_on ? ADDR_EWEN : ADDR_EWDS) << (edev->addrlen - 6); if (has_quirk_instruction_length(edev)) { cmd_addr <<= 2; @@ -328,10 +326,7 @@ static int eeprom_93xx46_eral(struct eeprom_93xx46_dev *edev) bits = edev->addrlen + 3; cmd_addr = OP_START << edev->addrlen; - if (edev->pdata->flags & EE_ADDR8) - cmd_addr |= ADDR_ERAL << 1; - else - cmd_addr |= ADDR_ERAL; + cmd_addr |= ADDR_ERAL << (edev->addrlen - 6); if (has_quirk_instruction_length(edev)) { cmd_addr <<= 2; From ba75ecb97d3f4e95d59002c13afb6519205be6cb Mon Sep 17 00:00:00 2001 From: Tuo Li Date: Thu, 11 Dec 2025 14:36:37 +0800 Subject: [PATCH 079/297] misc: bcm_vk: Fix possible null-pointer dereferences in bcm_vk_read() In the function bcm_vk_read(), the pointer entry is checked, indicating that it can be NULL. If entry is NULL and rc is set to -EMSGSIZE, the following code may cause null-pointer dereferences: struct vk_msg_blk tmp_msg = entry->to_h_msg[0]; set_msg_id(&tmp_msg, entry->usr_msg_id); tmp_msg.size = entry->to_h_blks - 1; To prevent these possible null-pointer dereferences, copy to_h_msg, usr_msg_id, and to_h_blks from iter into temporary variables, and return these temporary variables to the application instead of accessing them through a potentially NULL entry. Signed-off-by: Tuo Li Reviewed-by: Scott Branden Link: https://patch.msgid.link/20251211063637.3987937-1-islituo@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/bcm-vk/bcm_vk_msg.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/misc/bcm-vk/bcm_vk_msg.c b/drivers/misc/bcm-vk/bcm_vk_msg.c index 1f42d1d5a630..665a3888708a 100644 --- a/drivers/misc/bcm-vk/bcm_vk_msg.c +++ b/drivers/misc/bcm-vk/bcm_vk_msg.c @@ -1010,6 +1010,9 @@ ssize_t bcm_vk_read(struct file *p_file, struct device *dev = &vk->pdev->dev; struct bcm_vk_msg_chan *chan = &vk->to_h_msg_chan; struct bcm_vk_wkent *entry = NULL, *iter; + struct vk_msg_blk tmp_msg; + u32 tmp_usr_msg_id; + u32 tmp_blks; u32 q_num; u32 rsp_length; @@ -1034,6 +1037,9 @@ ssize_t bcm_vk_read(struct file *p_file, entry = iter; } else { /* buffer not big enough */ + tmp_msg = iter->to_h_msg[0]; + tmp_usr_msg_id = iter->usr_msg_id; + tmp_blks = iter->to_h_blks; rc = -EMSGSIZE; } goto read_loop_exit; @@ -1052,14 +1058,12 @@ read_loop_exit: bcm_vk_free_wkent(dev, entry); } else if (rc == -EMSGSIZE) { - struct vk_msg_blk tmp_msg = entry->to_h_msg[0]; - /* * in this case, return just the first block, so * that app knows what size it is looking for. */ - set_msg_id(&tmp_msg, entry->usr_msg_id); - tmp_msg.size = entry->to_h_blks - 1; + set_msg_id(&tmp_msg, tmp_usr_msg_id); + tmp_msg.size = tmp_blks - 1; if (copy_to_user(buf, &tmp_msg, VK_MSGQ_BLK_SIZE) != 0) { dev_err(dev, "Error return 1st block in -EMSGSIZE\n"); rc = -EFAULT; From 40fc797ba18328e57ed1cb213b4b5e48f86f4c7c Mon Sep 17 00:00:00 2001 From: Carlos Llamas Date: Mon, 15 Dec 2025 18:17:09 +0000 Subject: [PATCH 080/297] binder: fix trivial typo in uapi header As reported by codespell: include/uapi/linux/android/binder.h:281: interupted ==> interrupted Signed-off-by: Carlos Llamas Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20251215181724.3811977-1-cmllamas@google.com Signed-off-by: Greg Kroah-Hartman --- include/uapi/linux/android/binder.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/uapi/linux/android/binder.h b/include/uapi/linux/android/binder.h index 03ee4c7010d7..701cad36de43 100644 --- a/include/uapi/linux/android/binder.h +++ b/include/uapi/linux/android/binder.h @@ -278,7 +278,7 @@ enum { * NOTE: Two special error codes you should check for when calling * in to the driver are: * - * EINTR -- The operation has been interupted. This should be + * EINTR -- The operation has been interrupted. This should be * handled by retrying the ioctl() until a different error code * is returned. * From dad9f13d967b4e53e8eaf5f9c690f8e778ad9802 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Fri, 26 Dec 2025 18:22:43 +0800 Subject: [PATCH 081/297] misc: ti_fpc202: fix a potential memory leak in probe function Use for_each_child_of_node_scoped() to simplify the code and ensure the device node reference is automatically released when the loop scope ends. Signed-off-by: Felix Gu Reviewed-by: Romain Gantois Link: https://patch.msgid.link/tencent_FA1AC670F5CF49873F88A44424F866994A08@qq.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/ti_fpc202.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/misc/ti_fpc202.c b/drivers/misc/ti_fpc202.c index 7964e46c7448..8eb2b5ac9850 100644 --- a/drivers/misc/ti_fpc202.c +++ b/drivers/misc/ti_fpc202.c @@ -309,7 +309,6 @@ static void fpc202_remove_port(struct fpc202_priv *priv, int port_id) static int fpc202_probe(struct i2c_client *client) { struct device *dev = &client->dev; - struct device_node *i2c_handle; struct fpc202_priv *priv; int ret, port_id; @@ -357,7 +356,7 @@ static int fpc202_probe(struct i2c_client *client) bitmap_zero(priv->probed_ports, FPC202_NUM_PORTS); - for_each_child_of_node(dev->of_node, i2c_handle) { + for_each_child_of_node_scoped(dev->of_node, i2c_handle) { ret = of_property_read_u32(i2c_handle, "reg", &port_id); if (ret) { if (ret == -EINVAL) From e849ada70c6b1ee22e9f4f5c0e38231dcee53f04 Mon Sep 17 00:00:00 2001 From: Alper Ak Date: Sat, 27 Dec 2025 02:02:48 +0300 Subject: [PATCH 082/297] char: misc: Use IS_ERR() for filp_open() return value filp_open() never returns NULL, it returns either a valid pointer or an error pointer. Using IS_ERR_OR_NULL() is unnecessary. Additionally, if filp were NULL, PTR_ERR(NULL) would return 0, leading to a misleading error message. Fixes: 74d8361be344 ("char: misc: add test cases") Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/202506132058.thWZHlrb-lkp@intel.com/ Signed-off-by: Alper Ak Acked-by: Thadeu Lima de Souza Cascardo Link: https://patch.msgid.link/20251226230248.113073-1-alperyasinak1@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/char/misc_minor_kunit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/char/misc_minor_kunit.c b/drivers/char/misc_minor_kunit.c index 6fc8b05169c5..e930c78e1ef9 100644 --- a/drivers/char/misc_minor_kunit.c +++ b/drivers/char/misc_minor_kunit.c @@ -166,7 +166,7 @@ static void __init miscdev_test_can_open(struct kunit *test, struct miscdevice * KUNIT_FAIL(test, "failed to create node\n"); filp = filp_open(devname, O_RDONLY, 0); - if (IS_ERR_OR_NULL(filp)) + if (IS_ERR(filp)) KUNIT_FAIL(test, "failed to open misc device: %ld\n", PTR_ERR(filp)); else fput(filp); From 953deba747911225bcb936b3fa1cd02014910b17 Mon Sep 17 00:00:00 2001 From: Atharv Dubey Date: Sat, 29 Nov 2025 17:45:13 +0530 Subject: [PATCH 083/297] rust: miscdevice: use `pin_init::zeroed()` for C type initialization Replace manual zero-initialization using `MaybeUninit::zeroed().assume_init()` with `pin_init::zeroed()`. The `pin_init` helper provides a safer and clearer API for zero-initializing C structs without requiring an `unsafe` block. Link: https://github.com/Rust-for-Linux/linux/issues/1189 Signed-off-by: Atharv Dubey Reviewed-by: Alexandre Courbot Link: https://patch.msgid.link/20251129121513.20738-1-atharvd440@gmail.com Signed-off-by: Greg Kroah-Hartman --- rust/kernel/miscdevice.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/rust/kernel/miscdevice.rs b/rust/kernel/miscdevice.rs index d698cddcb4a5..ba64c8a858f0 100644 --- a/rust/kernel/miscdevice.rs +++ b/rust/kernel/miscdevice.rs @@ -20,7 +20,7 @@ use crate::{ seq_file::SeqFile, types::{ForeignOwnable, Opaque}, }; -use core::{marker::PhantomData, mem::MaybeUninit, pin::Pin}; +use core::{marker::PhantomData, pin::Pin}; /// Options for creating a misc device. #[derive(Copy, Clone)] @@ -32,8 +32,7 @@ pub struct MiscDeviceOptions { impl MiscDeviceOptions { /// Create a raw `struct miscdev` ready for registration. pub const fn into_raw(self) -> bindings::miscdevice { - // SAFETY: All zeros is valid for this C type. - let mut result: bindings::miscdevice = unsafe { MaybeUninit::zeroed().assume_init() }; + let mut result: bindings::miscdevice = pin_init::zeroed(); result.minor = bindings::MISC_DYNAMIC_MINOR as ffi::c_int; result.name = crate::str::as_char_ptr_in_const_context(self.name); result.fops = MiscdeviceVTable::::build(); @@ -420,8 +419,7 @@ impl MiscdeviceVTable { } else { None }, - // SAFETY: All zeros is a valid value for `bindings::file_operations`. - ..unsafe { MaybeUninit::zeroed().assume_init() } + ..pin_init::zeroed() }; const fn build() -> &'static bindings::file_operations { From 0c4ce29612bcd1f74d924cbc4abd133dc002e5e6 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 2 Dec 2025 19:37:27 +0000 Subject: [PATCH 084/297] rust: binder: add __rust_helper to helpers This is needed to inline these helpers into Rust code. Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20251202-define-rust-helper-v1-3-a2e13cbc17a6@google.com Signed-off-by: Greg Kroah-Hartman --- rust/helpers/binder.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/rust/helpers/binder.c b/rust/helpers/binder.c index 224d38a92f1d..a2327f1b3c94 100644 --- a/rust/helpers/binder.c +++ b/rust/helpers/binder.c @@ -7,20 +7,21 @@ #include #include -unsigned long rust_helper_list_lru_count(struct list_lru *lru) +__rust_helper unsigned long rust_helper_list_lru_count(struct list_lru *lru) { return list_lru_count(lru); } -unsigned long rust_helper_list_lru_walk(struct list_lru *lru, - list_lru_walk_cb isolate, void *cb_arg, - unsigned long nr_to_walk) +__rust_helper unsigned long rust_helper_list_lru_walk(struct list_lru *lru, + list_lru_walk_cb isolate, + void *cb_arg, + unsigned long nr_to_walk) { return list_lru_walk(lru, isolate, cb_arg, nr_to_walk); } -void rust_helper_init_task_work(struct callback_head *twork, - task_work_func_t func) +__rust_helper void rust_helper_init_task_work(struct callback_head *twork, + task_work_func_t func) { init_task_work(twork, func); } From c1093b85890693fa2d372468f279854c3624d2b1 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 3 Dec 2025 14:48:08 +0000 Subject: [PATCH 085/297] rust: sync: add Arc::DATA_OFFSET This constant will be used to expose some offset constants from the Rust Binder driver to tracepoints which are implemented in C. The constant is usually equal to sizeof(refcount_t), but may be larger if T has a large alignment. Signed-off-by: Alice Ryhl Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20251203-binder-trace1-v1-1-22d3ffddb44e@google.com Signed-off-by: Greg Kroah-Hartman --- rust/kernel/sync/arc.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rust/kernel/sync/arc.rs b/rust/kernel/sync/arc.rs index 289f77abf415..921e19333b89 100644 --- a/rust/kernel/sync/arc.rs +++ b/rust/kernel/sync/arc.rs @@ -240,6 +240,9 @@ impl Arc { // `Arc` object. Ok(unsafe { Self::from_inner(inner) }) } + + /// The offset that the value is stored at. + pub const DATA_OFFSET: usize = core::mem::offset_of!(ArcInner, data); } impl Arc { From c1ea31205edf2fd748bc6ceb081033bcfa0a869a Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 3 Dec 2025 14:48:09 +0000 Subject: [PATCH 086/297] rust_binder: add binder_transaction tracepoint This patch adds the binder_transaction tracepoint to Rust Binder. This was chosen as the next tracepoint to add as it is the most complex tracepoint. (And it's also an important tracepoint known to perfetto.) Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20251203-binder-trace1-v1-2-22d3ffddb44e@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/node.rs | 8 +++ drivers/android/binder/process.rs | 7 ++ drivers/android/binder/rust_binder.h | 79 +++++++++++++++++++++ drivers/android/binder/rust_binder_events.h | 30 ++++++++ drivers/android/binder/rust_binder_main.rs | 8 +++ drivers/android/binder/thread.rs | 1 + drivers/android/binder/trace.rs | 21 ++++++ drivers/android/binder/transaction.rs | 14 ++++ 8 files changed, 168 insertions(+) diff --git a/drivers/android/binder/node.rs b/drivers/android/binder/node.rs index c26d113ede96..69f757ff7461 100644 --- a/drivers/android/binder/node.rs +++ b/drivers/android/binder/node.rs @@ -178,6 +178,14 @@ struct NodeInner { refs: List, } +use kernel::bindings::rb_node_layout; +use mem::offset_of; +pub(crate) const NODE_LAYOUT: rb_node_layout = rb_node_layout { + arc_offset: Arc::::DATA_OFFSET + offset_of!(DTRWrap, wrapped), + debug_id: offset_of!(Node, debug_id), + ptr: offset_of!(Node, ptr), +}; + #[pin_data] pub(crate) struct Node { pub(crate) debug_id: usize, diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs index 132055b4790f..62b8279b711f 100644 --- a/drivers/android/binder/process.rs +++ b/drivers/android/binder/process.rs @@ -418,6 +418,13 @@ impl ProcessNodeRefs { } } +use core::mem::offset_of; +use kernel::bindings::rb_process_layout; +pub(crate) const PROCESS_LAYOUT: rb_process_layout = rb_process_layout { + arc_offset: Arc::::DATA_OFFSET, + task: offset_of!(Process, task), +}; + /// A process using binder. /// /// Strictly speaking, there can be multiple of these per process. There is one for each binder fd diff --git a/drivers/android/binder/rust_binder.h b/drivers/android/binder/rust_binder.h index 31806890ed1a..e68ba7a23c34 100644 --- a/drivers/android/binder/rust_binder.h +++ b/drivers/android/binder/rust_binder.h @@ -20,4 +20,83 @@ struct inode; struct dentry *rust_binderfs_create_proc_file(struct inode *nodp, int pid); void rust_binderfs_remove_file(struct dentry *dentry); +/* + * The internal data types in the Rust Binder driver are opaque to C, so we use + * void pointer typedefs for these types. + */ + +typedef void *rust_binder_transaction; +typedef void *rust_binder_process; +typedef void *rust_binder_node; + +struct rb_process_layout { + size_t arc_offset; + size_t task; +}; + +struct rb_transaction_layout { + size_t debug_id; + size_t code; + size_t flags; + size_t from_thread; + size_t to_proc; + size_t target_node; +}; + +struct rb_node_layout { + size_t arc_offset; + size_t debug_id; + size_t ptr; +}; + +struct rust_binder_layout { + struct rb_transaction_layout t; + struct rb_process_layout p; + struct rb_node_layout n; +}; + +extern const struct rust_binder_layout RUST_BINDER_LAYOUT; + +static inline size_t rust_binder_transaction_debug_id(rust_binder_transaction t) +{ + return *(size_t *) (t + RUST_BINDER_LAYOUT.t.debug_id); +} + +static inline u32 rust_binder_transaction_code(rust_binder_transaction t) +{ + return *(u32 *) (t + RUST_BINDER_LAYOUT.t.code); +} + +static inline u32 rust_binder_transaction_flags(rust_binder_transaction t) +{ + return *(u32 *) (t + RUST_BINDER_LAYOUT.t.flags); +} + +// Nullable! +static inline rust_binder_node rust_binder_transaction_target_node(rust_binder_transaction t) +{ + void *p = *(void **) (t + RUST_BINDER_LAYOUT.t.target_node); + + if (p) + p = p + RUST_BINDER_LAYOUT.n.arc_offset; + return NULL; +} + +static inline rust_binder_process rust_binder_transaction_to_proc(rust_binder_transaction t) +{ + void *p = *(void **) (t + RUST_BINDER_LAYOUT.t.to_proc); + + return p + RUST_BINDER_LAYOUT.p.arc_offset; +} + +static inline struct task_struct *rust_binder_process_task(rust_binder_process t) +{ + return *(struct task_struct **) (t + RUST_BINDER_LAYOUT.p.task); +} + +static inline size_t rust_binder_node_debug_id(rust_binder_node t) +{ + return *(size_t *) (t + RUST_BINDER_LAYOUT.n.debug_id); +} + #endif diff --git a/drivers/android/binder/rust_binder_events.h b/drivers/android/binder/rust_binder_events.h index 2f3efbf9dba6..8ad785c6bd0f 100644 --- a/drivers/android/binder/rust_binder_events.h +++ b/drivers/android/binder/rust_binder_events.h @@ -30,6 +30,36 @@ TRACE_EVENT(rust_binder_ioctl, TP_printk("cmd=0x%x arg=0x%lx", __entry->cmd, __entry->arg) ); +TRACE_EVENT(rust_binder_transaction, + TP_PROTO(bool reply, rust_binder_transaction t, struct task_struct *thread), + TP_ARGS(reply, t, thread), + TP_STRUCT__entry( + __field(int, debug_id) + __field(int, target_node) + __field(int, to_proc) + __field(int, to_thread) + __field(int, reply) + __field(unsigned int, code) + __field(unsigned int, flags) + ), + TP_fast_assign( + rust_binder_process to = rust_binder_transaction_to_proc(t); + rust_binder_node target_node = rust_binder_transaction_target_node(t); + + __entry->debug_id = rust_binder_transaction_debug_id(t); + __entry->target_node = target_node ? rust_binder_node_debug_id(target_node) : 0; + __entry->to_proc = rust_binder_process_task(to)->pid; + __entry->to_thread = thread ? thread->pid : 0; + __entry->reply = reply; + __entry->code = rust_binder_transaction_code(t); + __entry->flags = rust_binder_transaction_flags(t); + ), + TP_printk("transaction=%d dest_node=%d dest_proc=%d dest_thread=%d reply=%d flags=0x%x code=0x%x", + __entry->debug_id, __entry->target_node, + __entry->to_proc, __entry->to_thread, + __entry->reply, __entry->flags, __entry->code) +); + #endif /* _RUST_BINDER_TRACE_H */ /* This part must be outside protection */ diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs index c79a9e742240..c04b88936584 100644 --- a/drivers/android/binder/rust_binder_main.rs +++ b/drivers/android/binder/rust_binder_main.rs @@ -89,6 +89,14 @@ module! { license: "GPL", } +use kernel::bindings::rust_binder_layout; +#[no_mangle] +static RUST_BINDER_LAYOUT: rust_binder_layout = rust_binder_layout { + t: transaction::TRANSACTION_LAYOUT, + p: process::PROCESS_LAYOUT, + n: node::NODE_LAYOUT, +}; + fn next_debug_id() -> usize { static NEXT_DEBUG_ID: AtomicUsize = AtomicUsize::new(0); diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs index 1a8e6fdc0dc4..6e8f29e50cac 100644 --- a/drivers/android/binder/thread.rs +++ b/drivers/android/binder/thread.rs @@ -1117,6 +1117,7 @@ impl Thread { transaction: &DArc, ) -> bool { if let Ok(transaction) = &reply { + crate::trace::trace_transaction(true, transaction, Some(&self.task)); transaction.set_outstanding(&mut self.process.inner.lock()); } diff --git a/drivers/android/binder/trace.rs b/drivers/android/binder/trace.rs index af0e4392805e..9839901c7151 100644 --- a/drivers/android/binder/trace.rs +++ b/drivers/android/binder/trace.rs @@ -2,11 +2,21 @@ // Copyright (C) 2025 Google LLC. +use crate::transaction::Transaction; + +use kernel::bindings::{rust_binder_transaction, task_struct}; use kernel::ffi::{c_uint, c_ulong}; +use kernel::task::Task; use kernel::tracepoint::declare_trace; declare_trace! { unsafe fn rust_binder_ioctl(cmd: c_uint, arg: c_ulong); + unsafe fn rust_binder_transaction(reply: bool, t: rust_binder_transaction, thread: *mut task_struct); +} + +#[inline] +fn raw_transaction(t: &Transaction) -> rust_binder_transaction { + t as *const Transaction as rust_binder_transaction } #[inline] @@ -14,3 +24,14 @@ pub(crate) fn trace_ioctl(cmd: u32, arg: usize) { // SAFETY: Always safe to call. unsafe { rust_binder_ioctl(cmd, arg as c_ulong) } } + +#[inline] +pub(crate) fn trace_transaction(reply: bool, t: &Transaction, thread: Option<&Task>) { + let thread = match thread { + Some(thread) => thread.as_ptr(), + None => core::ptr::null_mut(), + }; + // SAFETY: The raw transaction is valid for the duration of this call. The thread pointer is + // valid or null. + unsafe { rust_binder_transaction(reply, raw_transaction(t), thread) } +} diff --git a/drivers/android/binder/transaction.rs b/drivers/android/binder/transaction.rs index 4bd3c0e417eb..cd8d8202e52d 100644 --- a/drivers/android/binder/transaction.rs +++ b/drivers/android/binder/transaction.rs @@ -24,6 +24,17 @@ use crate::{ BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverToRead, }; +use core::mem::offset_of; +use kernel::bindings::rb_transaction_layout; +pub(crate) const TRANSACTION_LAYOUT: rb_transaction_layout = rb_transaction_layout { + debug_id: offset_of!(Transaction, debug_id), + code: offset_of!(Transaction, code), + flags: offset_of!(Transaction, flags), + from_thread: offset_of!(Transaction, from), + to_proc: offset_of!(Transaction, to), + target_node: offset_of!(Transaction, target_node), +}; + #[pin_data(PinnedDrop)] pub(crate) struct Transaction { pub(crate) debug_id: usize, @@ -249,6 +260,7 @@ impl Transaction { if oneway { if let Some(target_node) = self.target_node.clone() { + crate::trace::trace_transaction(false, &self, None); if process_inner.is_frozen.is_frozen() { process_inner.async_recv = true; if self.flags & TF_UPDATE_TXN != 0 { @@ -286,11 +298,13 @@ impl Transaction { } let res = if let Some(thread) = self.find_target_thread() { + crate::trace::trace_transaction(false, &self, Some(&thread.task)); match thread.push_work(self) { PushWorkRes::Ok => Ok(()), PushWorkRes::FailedDead(me) => Err((BinderError::new_dead(), me)), } } else { + crate::trace::trace_transaction(false, &self, None); process_inner.push_work(self) }; drop(process_inner); From 582ce8ea201292ecc020505ab258991cdc7d0bd2 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Mon, 22 Dec 2025 13:21:23 +0100 Subject: [PATCH 087/297] rust: miscdevice: replace `kernel::c_str!` with C-Strings C-String literals were added in Rust 1.77. Replace instances of `kernel::c_str!` with C-String literals where possible. Acked-by: Greg Kroah-Hartman Reviewed-by: Alice Ryhl Reviewed-by: Benno Lossin Signed-off-by: Tamir Duberstein Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20251222-cstr-char-misc-v1-1-d218537d28ab@gmail.com Signed-off-by: Greg Kroah-Hartman --- samples/rust/rust_misc_device.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/samples/rust/rust_misc_device.rs b/samples/rust/rust_misc_device.rs index d69bc33dbd99..49dd5814e1ab 100644 --- a/samples/rust/rust_misc_device.rs +++ b/samples/rust/rust_misc_device.rs @@ -98,7 +98,6 @@ use core::pin::Pin; use kernel::{ - c_str, device::Device, fs::{File, Kiocb}, ioctl::{_IO, _IOC_SIZE, _IOR, _IOW}, @@ -133,7 +132,7 @@ impl kernel::InPlaceModule for RustMiscDeviceModule { pr_info!("Initialising Rust Misc Device Sample\n"); let options = MiscDeviceOptions { - name: c_str!("rust-misc-device"), + name: c"rust-misc-device", }; try_pin_init!(Self { From 46c549ef78899addd7c6adc407621640778fad1e Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Mon, 22 Dec 2025 13:22:17 +0100 Subject: [PATCH 088/297] rust_binder: replace `kernel::c_str!` with C-Strings C-String literals were added in Rust 1.77. Replace instances of `kernel::c_str!` with C-String literals where possible. Signed-off-by: Tamir Duberstein Reviewed-by: Daniel Almeida Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20251222-cstr-staging-v1-1-974149ba4a79@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/rust_binder_main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs index c04b88936584..08060ecc9ba4 100644 --- a/drivers/android/binder/rust_binder_main.rs +++ b/drivers/android/binder/rust_binder_main.rs @@ -296,7 +296,7 @@ impl kernel::Module for BinderModule { pr_warn!("Loaded Rust Binder."); - BINDER_SHRINKER.register(kernel::c_str!("android-binder"))?; + BINDER_SHRINKER.register(c"android-binder")?; // SAFETY: The module is being loaded, so we can initialize binderfs. unsafe { kernel::error::to_result(binderfs::init_rust_binderfs())? }; From 174e2a339bf731e080ced67c215ad609a677560b Mon Sep 17 00:00:00 2001 From: Xi Ruoyao Date: Tue, 9 Dec 2025 20:50:19 +0800 Subject: [PATCH 089/297] rust_binder: Fix build failure if !CONFIG_COMPAT The bindgen utility cannot handle "#define compat_ptr_ioctl NULL" in the C header, so we need to handle this case on our own. Simply skip this field in the initializer when !CONFIG_COMPAT as the SAFETY comment above this initializer implies this is allowed. Reported-by: Miguel Ojeda Closes: https://lore.kernel.org/all/CANiq72mrVzqXnAV=Hy2XBOonLHA6YQgH-ckZoc_h0VBvTGK8rA@mail.gmail.com/ Signed-off-by: Xi Ruoyao Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20251209125029.1117897-1-xry111@xry111.site Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/rust_binder_main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs index 08060ecc9ba4..d84c3c360be0 100644 --- a/drivers/android/binder/rust_binder_main.rs +++ b/drivers/android/binder/rust_binder_main.rs @@ -322,6 +322,7 @@ pub static rust_binder_fops: AssertSync = { owner: THIS_MODULE.as_ptr(), poll: Some(rust_binder_poll), unlocked_ioctl: Some(rust_binder_ioctl), + #[cfg(CONFIG_COMPAT)] compat_ioctl: Some(bindings::compat_ptr_ioctl), mmap: Some(rust_binder_mmap), open: Some(rust_binder_open), From 53da3f51e491c8aeee25004be9d95c30d5efdf5b Mon Sep 17 00:00:00 2001 From: Kumari Pallavi Date: Fri, 26 Dec 2025 12:35:31 +0530 Subject: [PATCH 090/297] dt-bindings: misc: qcom,fastrpc: Add compatible for Kaanapali Kaanapali introduces changes in DSP IOVA layout and CDSP DMA addressing that differ from previous SoCs. The SID field moves within the physical address, and CDSP now supports a wider DMA range, requiring updated sid_pos and DMA mask handling in the driver. Signed-off-by: Kumari Pallavi Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20251226070534.602021-2-kumari.pallavi@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/misc/qcom,fastrpc.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/misc/qcom,fastrpc.yaml b/Documentation/devicetree/bindings/misc/qcom,fastrpc.yaml index 3f6199fc9ae6..d8e47db677cc 100644 --- a/Documentation/devicetree/bindings/misc/qcom,fastrpc.yaml +++ b/Documentation/devicetree/bindings/misc/qcom,fastrpc.yaml @@ -18,7 +18,9 @@ description: | properties: compatible: - const: qcom,fastrpc + enum: + - qcom,kaanapali-fastrpc + - qcom,fastrpc label: enum: From 428b2f2b60c37f69e0723a225135da1f352d2602 Mon Sep 17 00:00:00 2001 From: Kumari Pallavi Date: Fri, 26 Dec 2025 12:35:32 +0530 Subject: [PATCH 091/297] misc: fastrpc: Rename phys to dma_addr for clarity The fields buf->phys and map->phys currently store DMA addresses returned by dma_map_*() APIs, not physical addresses. This naming is misleading and may lead to incorrect assumptions about the address type and its translation. Rename these fields from phys to dma_addr to improve code clarity and align with kernel conventions for dma_addr_t usage. Signed-off-by: Kumari Pallavi Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20251226070534.602021-3-kumari.pallavi@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/fastrpc.c | 77 ++++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index ee652ef01534..eb9501fe79bc 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -106,7 +106,7 @@ #define miscdev_to_fdevice(d) container_of(d, struct fastrpc_device, miscdev) struct fastrpc_phy_page { - u64 addr; /* physical address */ + dma_addr_t addr; /* dma address */ u64 size; /* size of contiguous region */ }; @@ -171,7 +171,7 @@ struct fastrpc_msg { u64 ctx; /* invoke caller context */ u32 handle; /* handle to invoke */ u32 sc; /* scalars structure describing the data */ - u64 addr; /* physical address */ + dma_addr_t addr; /* dma address */ u64 size; /* size of contiguous region */ }; @@ -194,7 +194,7 @@ struct fastrpc_buf { struct dma_buf *dmabuf; struct device *dev; void *virt; - u64 phys; + dma_addr_t dma_addr; u64 size; /* Lock for dma buf attachments */ struct mutex lock; @@ -217,7 +217,7 @@ struct fastrpc_map { struct dma_buf *buf; struct sg_table *table; struct dma_buf_attachment *attach; - u64 phys; + dma_addr_t dma_addr; u64 size; void *va; u64 len; @@ -320,11 +320,12 @@ static void fastrpc_free_map(struct kref *ref) perm.vmid = QCOM_SCM_VMID_HLOS; perm.perm = QCOM_SCM_PERM_RWX; - err = qcom_scm_assign_mem(map->phys, map->len, + err = qcom_scm_assign_mem(map->dma_addr, map->len, &src_perms, &perm, 1); if (err) { - dev_err(map->fl->sctx->dev, "Failed to assign memory phys 0x%llx size 0x%llx err %d\n", - map->phys, map->len, err); + dev_err(map->fl->sctx->dev, + "Failed to assign memory dma_addr %pad size 0x%llx err %d\n", + &map->dma_addr, map->len, err); return; } } @@ -389,7 +390,7 @@ static int fastrpc_map_lookup(struct fastrpc_user *fl, int fd, static void fastrpc_buf_free(struct fastrpc_buf *buf) { dma_free_coherent(buf->dev, buf->size, buf->virt, - FASTRPC_PHYS(buf->phys)); + FASTRPC_PHYS(buf->dma_addr)); kfree(buf); } @@ -408,12 +409,12 @@ static int __fastrpc_buf_alloc(struct fastrpc_user *fl, struct device *dev, buf->fl = fl; buf->virt = NULL; - buf->phys = 0; + buf->dma_addr = 0; buf->size = size; buf->dev = dev; buf->raddr = 0; - buf->virt = dma_alloc_coherent(dev, buf->size, (dma_addr_t *)&buf->phys, + buf->virt = dma_alloc_coherent(dev, buf->size, &buf->dma_addr, GFP_KERNEL); if (!buf->virt) { mutex_destroy(&buf->lock); @@ -439,7 +440,7 @@ static int fastrpc_buf_alloc(struct fastrpc_user *fl, struct device *dev, buf = *obuf; if (fl->sctx && fl->sctx->sid) - buf->phys += ((u64)fl->sctx->sid << 32); + buf->dma_addr += ((u64)fl->sctx->sid << 32); return 0; } @@ -684,7 +685,7 @@ static int fastrpc_dma_buf_attach(struct dma_buf *dmabuf, return -ENOMEM; ret = dma_get_sgtable(buffer->dev, &a->sgt, buffer->virt, - FASTRPC_PHYS(buffer->phys), buffer->size); + FASTRPC_PHYS(buffer->dma_addr), buffer->size); if (ret < 0) { dev_err(buffer->dev, "failed to get scatterlist from DMA API\n"); kfree(a); @@ -733,7 +734,7 @@ static int fastrpc_mmap(struct dma_buf *dmabuf, dma_resv_assert_held(dmabuf->resv); return dma_mmap_coherent(buf->dev, vma, buf->virt, - FASTRPC_PHYS(buf->phys), size); + FASTRPC_PHYS(buf->dma_addr), size); } static const struct dma_buf_ops fastrpc_dma_buf_ops = { @@ -785,10 +786,10 @@ static int fastrpc_map_attach(struct fastrpc_user *fl, int fd, map->table = table; if (attr & FASTRPC_ATTR_SECUREMAP) { - map->phys = sg_phys(map->table->sgl); + map->dma_addr = sg_phys(map->table->sgl); } else { - map->phys = sg_dma_address(map->table->sgl); - map->phys += ((u64)fl->sctx->sid << 32); + map->dma_addr = sg_dma_address(map->table->sgl); + map->dma_addr += ((u64)fl->sctx->sid << 32); } for_each_sg(map->table->sgl, sgl, map->table->nents, sgl_index) @@ -815,10 +816,11 @@ static int fastrpc_map_attach(struct fastrpc_user *fl, int fd, dst_perms[1].vmid = fl->cctx->vmperms[0].vmid; dst_perms[1].perm = QCOM_SCM_PERM_RWX; map->attr = attr; - err = qcom_scm_assign_mem(map->phys, (u64)map->len, &src_perms, dst_perms, 2); + err = qcom_scm_assign_mem(map->dma_addr, (u64)map->len, &src_perms, dst_perms, 2); if (err) { - dev_err(sess->dev, "Failed to assign memory with phys 0x%llx size 0x%llx err %d\n", - map->phys, map->len, err); + dev_err(sess->dev, + "Failed to assign memory with dma_addr %pad size 0x%llx err %d\n", + &map->dma_addr, map->len, err); goto map_err; } } @@ -1009,7 +1011,7 @@ static int fastrpc_get_args(u32 kernel, struct fastrpc_invoke_ctx *ctx) struct vm_area_struct *vma = NULL; rpra[i].buf.pv = (u64) ctx->args[i].ptr; - pages[i].addr = ctx->maps[i]->phys; + pages[i].addr = ctx->maps[i]->dma_addr; mmap_read_lock(current->mm); vma = find_vma(current->mm, ctx->args[i].ptr); @@ -1036,7 +1038,7 @@ static int fastrpc_get_args(u32 kernel, struct fastrpc_invoke_ctx *ctx) goto bail; rpra[i].buf.pv = args - ctx->olaps[oix].offset; - pages[i].addr = ctx->buf->phys - + pages[i].addr = ctx->buf->dma_addr - ctx->olaps[oix].offset + (pkt_size - rlen); pages[i].addr = pages[i].addr & PAGE_MASK; @@ -1068,7 +1070,7 @@ static int fastrpc_get_args(u32 kernel, struct fastrpc_invoke_ctx *ctx) list[i].num = ctx->args[i].length ? 1 : 0; list[i].pgidx = i; if (ctx->maps[i]) { - pages[i].addr = ctx->maps[i]->phys; + pages[i].addr = ctx->maps[i]->dma_addr; pages[i].size = ctx->maps[i]->size; } rpra[i].dma.fd = ctx->args[i].fd; @@ -1150,7 +1152,7 @@ static int fastrpc_invoke_send(struct fastrpc_session_ctx *sctx, msg->ctx = ctx->ctxid | fl->pd; msg->handle = handle; msg->sc = ctx->sc; - msg->addr = ctx->buf ? ctx->buf->phys : 0; + msg->addr = ctx->buf ? ctx->buf->dma_addr : 0; msg->size = roundup(ctx->msg_sz, PAGE_SIZE); fastrpc_context_get(ctx); @@ -1306,13 +1308,15 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl, if (fl->cctx->vmcount) { u64 src_perms = BIT(QCOM_SCM_VMID_HLOS); - err = qcom_scm_assign_mem(fl->cctx->remote_heap->phys, + err = qcom_scm_assign_mem(fl->cctx->remote_heap->dma_addr, (u64)fl->cctx->remote_heap->size, &src_perms, fl->cctx->vmperms, fl->cctx->vmcount); if (err) { - dev_err(fl->sctx->dev, "Failed to assign memory with phys 0x%llx size 0x%llx err %d\n", - fl->cctx->remote_heap->phys, fl->cctx->remote_heap->size, err); + dev_err(fl->sctx->dev, + "Failed to assign memory with dma_addr %pad size 0x%llx err %d\n", + &fl->cctx->remote_heap->dma_addr, + fl->cctx->remote_heap->size, err); goto err_map; } scm_done = true; @@ -1332,7 +1336,7 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl, args[1].length = inbuf.namelen; args[1].fd = -1; - pages[0].addr = fl->cctx->remote_heap->phys; + pages[0].addr = fl->cctx->remote_heap->dma_addr; pages[0].size = fl->cctx->remote_heap->size; args[2].ptr = (u64)(uintptr_t) pages; @@ -1361,12 +1365,12 @@ err_invoke: dst_perms.vmid = QCOM_SCM_VMID_HLOS; dst_perms.perm = QCOM_SCM_PERM_RWX; - err = qcom_scm_assign_mem(fl->cctx->remote_heap->phys, + err = qcom_scm_assign_mem(fl->cctx->remote_heap->dma_addr, (u64)fl->cctx->remote_heap->size, &src_perms, &dst_perms, 1); if (err) - dev_err(fl->sctx->dev, "Failed to assign memory phys 0x%llx size 0x%llx err %d\n", - fl->cctx->remote_heap->phys, fl->cctx->remote_heap->size, err); + dev_err(fl->sctx->dev, "Failed to assign memory dma_addr %pad size 0x%llx err %d\n", + &fl->cctx->remote_heap->dma_addr, fl->cctx->remote_heap->size, err); } err_map: fastrpc_buf_free(fl->cctx->remote_heap); @@ -1455,7 +1459,7 @@ static int fastrpc_init_create_process(struct fastrpc_user *fl, args[2].length = inbuf.filelen; args[2].fd = init.filefd; - pages[0].addr = imem->phys; + pages[0].addr = imem->dma_addr; pages[0].size = imem->size; args[3].ptr = (u64)(uintptr_t) pages; @@ -1913,7 +1917,7 @@ static int fastrpc_req_mmap(struct fastrpc_user *fl, char __user *argp) args[0].ptr = (u64) (uintptr_t) &req_msg; args[0].length = sizeof(req_msg); - pages.addr = buf->phys; + pages.addr = buf->dma_addr; pages.size = buf->size; args[1].ptr = (u64) (uintptr_t) &pages; @@ -1941,11 +1945,12 @@ static int fastrpc_req_mmap(struct fastrpc_user *fl, char __user *argp) if (req.flags == ADSP_MMAP_REMOTE_HEAP_ADDR && fl->cctx->vmcount) { u64 src_perms = BIT(QCOM_SCM_VMID_HLOS); - err = qcom_scm_assign_mem(buf->phys, (u64)buf->size, + err = qcom_scm_assign_mem(buf->dma_addr, (u64)buf->size, &src_perms, fl->cctx->vmperms, fl->cctx->vmcount); if (err) { - dev_err(fl->sctx->dev, "Failed to assign memory phys 0x%llx size 0x%llx err %d", - buf->phys, buf->size, err); + dev_err(fl->sctx->dev, + "Failed to assign memory dma_addr %pad size 0x%llx err %d", + &buf->dma_addr, buf->size, err); goto err_assign; } } @@ -2059,7 +2064,7 @@ static int fastrpc_req_mem_map(struct fastrpc_user *fl, char __user *argp) args[0].ptr = (u64) (uintptr_t) &req_msg; args[0].length = sizeof(req_msg); - pages.addr = map->phys; + pages.addr = map->dma_addr; pages.size = map->len; args[1].ptr = (u64) (uintptr_t) &pages; From 1d94ce8996d71d77e2d649db9e5c205f423e2c17 Mon Sep 17 00:00:00 2001 From: Kumari Pallavi Date: Fri, 26 Dec 2025 12:35:33 +0530 Subject: [PATCH 092/297] misc: fastrpc: Add support for new DSP IOVA formatting Implement the new IOVA formatting required by the DSP architecture change on Kaanapali SoC. Place the SID for DSP DMA transactions at bit 56 in the physical address. This placement is necessary for the DSPs to correctly identify streams and operate as intended. To address this, set SID position to bit 56 via OF matching on the fastrpc node; otherwise, default to legacy 32-bit placement. This change ensures consistent SID placement across DSPs. Signed-off-by: Kumari Pallavi Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20251226070534.602021-4-kumari.pallavi@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/fastrpc.c | 62 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index eb9501fe79bc..fcc80206b7c4 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -22,6 +22,7 @@ #include #include #include +#include #define ADSP_DOMAIN_ID (0) #define MDSP_DOMAIN_ID (1) @@ -33,7 +34,6 @@ #define FASTRPC_ALIGN 128 #define FASTRPC_MAX_FDLIST 16 #define FASTRPC_MAX_CRCLIST 64 -#define FASTRPC_PHYS(p) ((p) & 0xffffffff) #define FASTRPC_CTX_MAX (256) #define FASTRPC_INIT_HANDLE 1 #define FASTRPC_DSP_UTILITIES_HANDLE 2 @@ -257,6 +257,10 @@ struct fastrpc_session_ctx { bool valid; }; +struct fastrpc_soc_data { + u32 sid_pos; +}; + struct fastrpc_channel_ctx { int domain_id; int sesscount; @@ -278,6 +282,7 @@ struct fastrpc_channel_ctx { bool secure; bool unsigned_support; u64 dma_mask; + const struct fastrpc_soc_data *soc_data; }; struct fastrpc_device { @@ -305,6 +310,24 @@ struct fastrpc_user { struct mutex mutex; }; +/* Extract SMMU PA from consolidated IOVA */ +static inline dma_addr_t fastrpc_ipa_to_dma_addr(struct fastrpc_channel_ctx *cctx, dma_addr_t iova) +{ + if (!cctx->soc_data->sid_pos) + return 0; + return iova & GENMASK_ULL(cctx->soc_data->sid_pos - 1, 0); +} + +/* + * Prepare the consolidated iova to send to DSP by prepending the SID + * to smmu PA at the appropriate position + */ +static inline u64 fastrpc_sid_offset(struct fastrpc_channel_ctx *cctx, + struct fastrpc_session_ctx *sctx) +{ + return (u64)sctx->sid << cctx->soc_data->sid_pos; +} + static void fastrpc_free_map(struct kref *ref) { struct fastrpc_map *map; @@ -390,7 +413,7 @@ static int fastrpc_map_lookup(struct fastrpc_user *fl, int fd, static void fastrpc_buf_free(struct fastrpc_buf *buf) { dma_free_coherent(buf->dev, buf->size, buf->virt, - FASTRPC_PHYS(buf->dma_addr)); + fastrpc_ipa_to_dma_addr(buf->fl->cctx, buf->dma_addr)); kfree(buf); } @@ -440,7 +463,7 @@ static int fastrpc_buf_alloc(struct fastrpc_user *fl, struct device *dev, buf = *obuf; if (fl->sctx && fl->sctx->sid) - buf->dma_addr += ((u64)fl->sctx->sid << 32); + buf->dma_addr += fastrpc_sid_offset(fl->cctx, fl->sctx); return 0; } @@ -685,7 +708,8 @@ static int fastrpc_dma_buf_attach(struct dma_buf *dmabuf, return -ENOMEM; ret = dma_get_sgtable(buffer->dev, &a->sgt, buffer->virt, - FASTRPC_PHYS(buffer->dma_addr), buffer->size); + fastrpc_ipa_to_dma_addr(buffer->fl->cctx, buffer->dma_addr), + buffer->size); if (ret < 0) { dev_err(buffer->dev, "failed to get scatterlist from DMA API\n"); kfree(a); @@ -734,7 +758,7 @@ static int fastrpc_mmap(struct dma_buf *dmabuf, dma_resv_assert_held(dmabuf->resv); return dma_mmap_coherent(buf->dev, vma, buf->virt, - FASTRPC_PHYS(buf->dma_addr), size); + fastrpc_ipa_to_dma_addr(buf->fl->cctx, buf->dma_addr), size); } static const struct dma_buf_ops fastrpc_dma_buf_ops = { @@ -747,6 +771,11 @@ static const struct dma_buf_ops fastrpc_dma_buf_ops = { .release = fastrpc_release, }; +static dma_addr_t fastrpc_compute_dma_addr(struct fastrpc_user *fl, dma_addr_t sg_dma_addr) +{ + return sg_dma_addr + fastrpc_sid_offset(fl->cctx, fl->sctx); +} + static int fastrpc_map_attach(struct fastrpc_user *fl, int fd, u64 len, u32 attr, struct fastrpc_map **ppmap) { @@ -785,12 +814,10 @@ static int fastrpc_map_attach(struct fastrpc_user *fl, int fd, } map->table = table; - if (attr & FASTRPC_ATTR_SECUREMAP) { + if (attr & FASTRPC_ATTR_SECUREMAP) map->dma_addr = sg_phys(map->table->sgl); - } else { - map->dma_addr = sg_dma_address(map->table->sgl); - map->dma_addr += ((u64)fl->sctx->sid << 32); - } + else + map->dma_addr = fastrpc_compute_dma_addr(fl, sg_dma_address(map->table->sgl)); for_each_sg(map->table->sgl, sgl, map->table->nents, sgl_index) map->size += sg_dma_len(sgl); @@ -2290,6 +2317,14 @@ static int fastrpc_get_domain_id(const char *domain) return -EINVAL; } +static const struct fastrpc_soc_data kaanapali_soc_data = { + .sid_pos = 56, +}; + +static const struct fastrpc_soc_data default_soc_data = { + .sid_pos = 32, +}; + static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) { struct device *rdev = &rpdev->dev; @@ -2298,6 +2333,9 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) const char *domain; bool secure_dsp; unsigned int vmids[FASTRPC_MAX_VMIDS]; + const struct fastrpc_soc_data *soc_data; + + soc_data = device_get_match_data(rdev); err = of_property_read_string(rdev->of_node, "label", &domain); if (err) { @@ -2350,6 +2388,7 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) secure_dsp = !(of_property_read_bool(rdev->of_node, "qcom,non-secure-domain")); data->secure = secure_dsp; + data->soc_data = soc_data; switch (domain_id) { case ADSP_DOMAIN_ID: @@ -2487,7 +2526,8 @@ static int fastrpc_rpmsg_callback(struct rpmsg_device *rpdev, void *data, } static const struct of_device_id fastrpc_rpmsg_of_match[] = { - { .compatible = "qcom,fastrpc" }, + { .compatible = "qcom,kaanapali-fastrpc", .data = &kaanapali_soc_data }, + { .compatible = "qcom,fastrpc", .data = &default_soc_data }, { }, }; MODULE_DEVICE_TABLE(of, fastrpc_rpmsg_of_match); From 8314d2c28d3369bc879af8e848f810292b16d0af Mon Sep 17 00:00:00 2001 From: Kumari Pallavi Date: Fri, 26 Dec 2025 12:35:34 +0530 Subject: [PATCH 093/297] misc: fastrpc: Update dma_bits for CDSP support on Kaanapali SoC DSP currently supports 32-bit IOVA (32-bit PA + 4-bit SID) for both Q6 and user DMA (uDMA) access. This is being upgraded to 34-bit PA + 4-bit SID due to a hardware revision in CDSP for Kaanapali SoC, which expands the DMA addressable range. Update DMA bits configuration in the driver to support CDSP on Kaanapali SoC. Set the default `dma_bits` to 32-bit and update it to 34-bit based on CDSP and OF matching on the fastrpc node. Signed-off-by: Kumari Pallavi Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20251226070534.602021-5-kumari.pallavi@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/misc/fastrpc.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index fcc80206b7c4..4f5a79c50f58 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -259,6 +259,8 @@ struct fastrpc_session_ctx { struct fastrpc_soc_data { u32 sid_pos; + u32 dma_addr_bits_cdsp; + u32 dma_addr_bits_default; }; struct fastrpc_channel_ctx { @@ -2197,6 +2199,7 @@ static int fastrpc_cb_probe(struct platform_device *pdev) int i, sessions = 0; unsigned long flags; int rc; + u32 dma_bits; cctx = dev_get_drvdata(dev->parent); if (!cctx) @@ -2210,12 +2213,16 @@ static int fastrpc_cb_probe(struct platform_device *pdev) spin_unlock_irqrestore(&cctx->lock, flags); return -ENOSPC; } + dma_bits = cctx->soc_data->dma_addr_bits_default; sess = &cctx->session[cctx->sesscount++]; sess->used = false; sess->valid = true; sess->dev = dev; dev_set_drvdata(dev, sess); + if (cctx->domain_id == CDSP_DOMAIN_ID) + dma_bits = cctx->soc_data->dma_addr_bits_cdsp; + if (of_property_read_u32(dev->of_node, "reg", &sess->sid)) dev_info(dev, "FastRPC Session ID not specified in DT\n"); @@ -2230,9 +2237,9 @@ static int fastrpc_cb_probe(struct platform_device *pdev) } } spin_unlock_irqrestore(&cctx->lock, flags); - rc = dma_set_mask(dev, DMA_BIT_MASK(32)); + rc = dma_set_mask(dev, DMA_BIT_MASK(dma_bits)); if (rc) { - dev_err(dev, "32-bit DMA enable failed\n"); + dev_err(dev, "%u-bit DMA enable failed\n", dma_bits); return rc; } @@ -2319,10 +2326,14 @@ static int fastrpc_get_domain_id(const char *domain) static const struct fastrpc_soc_data kaanapali_soc_data = { .sid_pos = 56, + .dma_addr_bits_cdsp = 34, + .dma_addr_bits_default = 32, }; static const struct fastrpc_soc_data default_soc_data = { .sid_pos = 32, + .dma_addr_bits_cdsp = 32, + .dma_addr_bits_default = 32, }; static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) From 51731792a25cb312ca94cdccfa139eb46de1b2ef Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 18 Dec 2025 22:21:44 +0530 Subject: [PATCH 094/297] net: qrtr: Drop the MHI auto_queue feature for IPCR DL channels MHI stack offers the 'auto_queue' feature, which allows the MHI stack to auto queue the buffers for the RX path (DL channel). Though this feature simplifies the client driver design, it introduces race between the client drivers and the MHI stack. For instance, with auto_queue, the 'dl_callback' for the DL channel may get called before the client driver is fully probed. This means, by the time the dl_callback gets called, the client driver's structures might not be initialized, leading to NULL ptr dereference. Currently, the drivers have to workaround this issue by initializing the internal structures before calling mhi_prepare_for_transfer_autoqueue(). But even so, there is a chance that the client driver's internal code path may call the MHI queue APIs before mhi_prepare_for_transfer_autoqueue() is called, leading to similar NULL ptr dereference. This issue has been reported on the Qcom X1E80100 CRD machines affecting boot. So to properly fix all these races, drop the MHI 'auto_queue' feature altogether and let the client driver (QRTR) manage the RX buffers manually. In the QRTR driver, queue the RX buffers based on the ring length during probe and recycle the buffers in 'dl_callback' once they are consumed. This also warrants removing the setting of 'auto_queue' flag from controller drivers. Currently, this 'auto_queue' feature is only enabled for IPCR DL channel. So only the QRTR client driver requires the modification. Fixes: 227fee5fc99e ("bus: mhi: core: Add an API for auto queueing buffers for DL channel") Fixes: 68a838b84eff ("net: qrtr: start MHI channel after endpoit creation") Reported-by: Johan Hovold Closes: https://lore.kernel.org/linux-arm-msm/ZyTtVdkCCES0lkl4@hovoldconsulting.com Suggested-by: Chris Lew Signed-off-by: Manivannan Sadhasivam Reviewed-by: Jeff Hugo Reviewed-by: Loic Poulain Acked-by: Jeff Johnson # drivers/net/wireless/ath/... Acked-by: Jeff Hugo Acked-by: Paolo Abeni Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251218-qrtr-fix-v2-1-c7499bfcfbe0@oss.qualcomm.com --- drivers/accel/qaic/mhi_controller.c | 44 ----------------- drivers/bus/mhi/host/pci_generic.c | 20 +------- drivers/net/wireless/ath/ath11k/mhi.c | 4 -- drivers/net/wireless/ath/ath12k/mhi.c | 4 -- net/qrtr/mhi.c | 69 ++++++++++++++++++++++----- 5 files changed, 60 insertions(+), 81 deletions(-) diff --git a/drivers/accel/qaic/mhi_controller.c b/drivers/accel/qaic/mhi_controller.c index 13a14c6c6168..4d787f77ce41 100644 --- a/drivers/accel/qaic/mhi_controller.c +++ b/drivers/accel/qaic/mhi_controller.c @@ -39,7 +39,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -55,7 +54,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -71,7 +69,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -87,7 +84,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -103,7 +99,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -119,7 +114,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -135,7 +129,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -151,7 +144,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -167,7 +159,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -183,7 +174,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -199,7 +189,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -215,7 +204,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -231,7 +219,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -247,7 +234,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -263,7 +249,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -279,7 +264,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -295,7 +279,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -311,7 +294,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -327,7 +309,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -343,7 +324,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -359,7 +339,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -375,7 +354,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -391,7 +369,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -407,7 +384,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -423,7 +399,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -439,7 +414,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = true, .wake_capable = false, }, }; @@ -458,7 +432,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -474,7 +447,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -490,7 +462,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -506,7 +477,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -522,7 +492,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -538,7 +507,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -554,7 +522,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -570,7 +537,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -586,7 +552,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -602,7 +567,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -618,7 +582,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -634,7 +597,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -650,7 +612,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -666,7 +627,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -682,7 +642,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -698,7 +657,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -714,7 +672,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -730,7 +687,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = true, .wake_capable = false, }, }; diff --git a/drivers/bus/mhi/host/pci_generic.c b/drivers/bus/mhi/host/pci_generic.c index e3bc737313a2..0884a384b77f 100644 --- a/drivers/bus/mhi/host/pci_generic.c +++ b/drivers/bus/mhi/host/pci_generic.c @@ -94,22 +94,6 @@ struct mhi_pci_dev_info { .doorbell_mode_switch = false, \ } -#define MHI_CHANNEL_CONFIG_DL_AUTOQUEUE(ch_num, ch_name, el_count, ev_ring) \ - { \ - .num = ch_num, \ - .name = ch_name, \ - .num_elements = el_count, \ - .event_ring = ev_ring, \ - .dir = DMA_FROM_DEVICE, \ - .ee_mask = BIT(MHI_EE_AMSS), \ - .pollcfg = 0, \ - .doorbell = MHI_DB_BRST_DISABLE, \ - .lpm_notify = false, \ - .offload_channel = false, \ - .doorbell_mode_switch = false, \ - .auto_queue = true, \ - } - #define MHI_EVENT_CONFIG_CTRL(ev_ring, el_count) \ { \ .num_elements = el_count, \ @@ -329,7 +313,7 @@ static const struct mhi_channel_config modem_qcom_v1_mhi_channels[] = { MHI_CHANNEL_CONFIG_UL(14, "QMI", 4, 0), MHI_CHANNEL_CONFIG_DL(15, "QMI", 4, 0), MHI_CHANNEL_CONFIG_UL(20, "IPCR", 8, 0), - MHI_CHANNEL_CONFIG_DL_AUTOQUEUE(21, "IPCR", 8, 0), + MHI_CHANNEL_CONFIG_DL(21, "IPCR", 8, 0), MHI_CHANNEL_CONFIG_UL_FP(34, "FIREHOSE", 32, 0), MHI_CHANNEL_CONFIG_DL_FP(35, "FIREHOSE", 32, 0), MHI_CHANNEL_CONFIG_UL(46, "IP_SW0", 64, 2), @@ -762,7 +746,7 @@ static const struct mhi_channel_config mhi_telit_fn980_hw_v1_channels[] = { MHI_CHANNEL_CONFIG_UL(14, "QMI", 32, 0), MHI_CHANNEL_CONFIG_DL(15, "QMI", 32, 0), MHI_CHANNEL_CONFIG_UL(20, "IPCR", 16, 0), - MHI_CHANNEL_CONFIG_DL_AUTOQUEUE(21, "IPCR", 16, 0), + MHI_CHANNEL_CONFIG_DL(21, "IPCR", 16, 0), MHI_CHANNEL_CONFIG_HW_UL(100, "IP_HW0", 128, 1), MHI_CHANNEL_CONFIG_HW_DL(101, "IP_HW0", 128, 2), }; diff --git a/drivers/net/wireless/ath/ath11k/mhi.c b/drivers/net/wireless/ath/ath11k/mhi.c index acd76e9392d3..d2c44f7f9b62 100644 --- a/drivers/net/wireless/ath/ath11k/mhi.c +++ b/drivers/net/wireless/ath/ath11k/mhi.c @@ -34,7 +34,6 @@ static const struct mhi_channel_config ath11k_mhi_channels_qca6390[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, }, { .num = 21, @@ -48,7 +47,6 @@ static const struct mhi_channel_config ath11k_mhi_channels_qca6390[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = true, }, }; @@ -99,7 +97,6 @@ static const struct mhi_channel_config ath11k_mhi_channels_qcn9074[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, }, { .num = 21, @@ -113,7 +110,6 @@ static const struct mhi_channel_config ath11k_mhi_channels_qcn9074[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = true, }, }; diff --git a/drivers/net/wireless/ath/ath12k/mhi.c b/drivers/net/wireless/ath/ath12k/mhi.c index 08f44baf182a..2dbdb95ae7be 100644 --- a/drivers/net/wireless/ath/ath12k/mhi.c +++ b/drivers/net/wireless/ath/ath12k/mhi.c @@ -31,7 +31,6 @@ static const struct mhi_channel_config ath12k_mhi_channels_qcn9274[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, }, { .num = 21, @@ -45,7 +44,6 @@ static const struct mhi_channel_config ath12k_mhi_channels_qcn9274[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = true, }, }; @@ -96,7 +94,6 @@ static const struct mhi_channel_config ath12k_mhi_channels_wcn7850[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, }, { .num = 21, @@ -110,7 +107,6 @@ static const struct mhi_channel_config ath12k_mhi_channels_wcn7850[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = true, }, }; diff --git a/net/qrtr/mhi.c b/net/qrtr/mhi.c index 69f53625a049..80e341d2f8a4 100644 --- a/net/qrtr/mhi.c +++ b/net/qrtr/mhi.c @@ -24,13 +24,25 @@ static void qcom_mhi_qrtr_dl_callback(struct mhi_device *mhi_dev, struct qrtr_mhi_dev *qdev = dev_get_drvdata(&mhi_dev->dev); int rc; - if (!qdev || mhi_res->transaction_status) + if (!qdev || (mhi_res->transaction_status && mhi_res->transaction_status != -ENOTCONN)) return; + /* Channel got reset. So just free the buffer */ + if (mhi_res->transaction_status == -ENOTCONN) { + devm_kfree(&mhi_dev->dev, mhi_res->buf_addr); + return; + } + rc = qrtr_endpoint_post(&qdev->ep, mhi_res->buf_addr, mhi_res->bytes_xferd); if (rc == -EINVAL) dev_err(qdev->dev, "invalid ipcrouter packet\n"); + + /* Done with the buffer, now recycle it for future use */ + rc = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE, mhi_res->buf_addr, + mhi_dev->mhi_cntrl->buffer_len, MHI_EOT); + if (rc) + dev_err(&mhi_dev->dev, "Failed to recycle the buffer: %d\n", rc); } /* From QRTR to MHI */ @@ -72,6 +84,29 @@ free_skb: return rc; } +static int qcom_mhi_qrtr_queue_dl_buffers(struct mhi_device *mhi_dev) +{ + u32 free_desc; + void *buf; + int ret; + + free_desc = mhi_get_free_desc_count(mhi_dev, DMA_FROM_DEVICE); + while (free_desc--) { + buf = devm_kmalloc(&mhi_dev->dev, mhi_dev->mhi_cntrl->buffer_len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE, buf, mhi_dev->mhi_cntrl->buffer_len, + MHI_EOT); + if (ret) { + dev_err(&mhi_dev->dev, "Failed to queue buffer: %d\n", ret); + return ret; + } + } + + return 0; +} + static int qcom_mhi_qrtr_probe(struct mhi_device *mhi_dev, const struct mhi_device_id *id) { @@ -87,20 +122,30 @@ static int qcom_mhi_qrtr_probe(struct mhi_device *mhi_dev, qdev->ep.xmit = qcom_mhi_qrtr_send; dev_set_drvdata(&mhi_dev->dev, qdev); - rc = qrtr_endpoint_register(&qdev->ep, QRTR_EP_NID_AUTO); + + /* start channels */ + rc = mhi_prepare_for_transfer(mhi_dev); if (rc) return rc; - /* start channels */ - rc = mhi_prepare_for_transfer_autoqueue(mhi_dev); - if (rc) { - qrtr_endpoint_unregister(&qdev->ep); - return rc; - } + rc = qrtr_endpoint_register(&qdev->ep, QRTR_EP_NID_AUTO); + if (rc) + goto err_unprepare; + + rc = qcom_mhi_qrtr_queue_dl_buffers(mhi_dev); + if (rc) + goto err_unregister; dev_dbg(qdev->dev, "Qualcomm MHI QRTR driver probed\n"); return 0; + +err_unregister: + qrtr_endpoint_unregister(&qdev->ep); +err_unprepare: + mhi_unprepare_from_transfer(mhi_dev); + + return rc; } static void qcom_mhi_qrtr_remove(struct mhi_device *mhi_dev) @@ -151,11 +196,13 @@ static int __maybe_unused qcom_mhi_qrtr_pm_resume_early(struct device *dev) if (state == MHI_STATE_M3) return 0; - rc = mhi_prepare_for_transfer_autoqueue(mhi_dev); - if (rc) + rc = mhi_prepare_for_transfer(mhi_dev); + if (rc) { dev_err(dev, "failed to prepare for autoqueue transfer %d\n", rc); + return rc; + } - return rc; + return qcom_mhi_qrtr_queue_dl_buffers(mhi_dev); } static const struct dev_pm_ops qcom_mhi_qrtr_pm_ops = { From 4a9ba211d0264131dcfca0cbc10bff5ff277ff0a Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 18 Dec 2025 22:21:45 +0530 Subject: [PATCH 095/297] bus: mhi: host: Drop the auto_queue support Now that the only user of the 'auto_queue' feature, (QRTR) has been converted to manage the buffers on its own, drop the code related to it. Signed-off-by: Manivannan Sadhasivam Reviewed-by: Loic Poulain Reviewed-by: Jeff Hugo Link: https://patch.msgid.link/20251218-qrtr-fix-v2-2-c7499bfcfbe0@oss.qualcomm.com --- drivers/bus/mhi/host/init.c | 10 ---- drivers/bus/mhi/host/internal.h | 3 -- drivers/bus/mhi/host/main.c | 81 +-------------------------------- include/linux/mhi.h | 14 ------ 4 files changed, 2 insertions(+), 106 deletions(-) diff --git a/drivers/bus/mhi/host/init.c b/drivers/bus/mhi/host/init.c index 099be8dd1900..b020a6489c07 100644 --- a/drivers/bus/mhi/host/init.c +++ b/drivers/bus/mhi/host/init.c @@ -841,18 +841,8 @@ static int parse_ch_cfg(struct mhi_controller *mhi_cntrl, mhi_chan->lpm_notify = ch_cfg->lpm_notify; mhi_chan->offload_ch = ch_cfg->offload_channel; mhi_chan->db_cfg.reset_req = ch_cfg->doorbell_mode_switch; - mhi_chan->pre_alloc = ch_cfg->auto_queue; mhi_chan->wake_capable = ch_cfg->wake_capable; - /* - * If MHI host allocates buffers, then the channel direction - * should be DMA_FROM_DEVICE - */ - if (mhi_chan->pre_alloc && mhi_chan->dir != DMA_FROM_DEVICE) { - dev_err(dev, "Invalid channel configuration\n"); - goto error_chan_cfg; - } - /* * Bi-directional and direction less channel must be an * offload channel diff --git a/drivers/bus/mhi/host/internal.h b/drivers/bus/mhi/host/internal.h index 7937bb1f742c..7b0ee5e3a12d 100644 --- a/drivers/bus/mhi/host/internal.h +++ b/drivers/bus/mhi/host/internal.h @@ -286,7 +286,6 @@ struct mhi_chan { bool lpm_notify; bool configured; bool offload_ch; - bool pre_alloc; bool wake_capable; }; @@ -389,8 +388,6 @@ int mhi_rddm_prepare(struct mhi_controller *mhi_cntrl, struct image_info *img_info); void mhi_fw_load_handler(struct mhi_controller *mhi_cntrl); -/* Automatically allocate and queue inbound buffers */ -#define MHI_CH_INBOUND_ALLOC_BUFS BIT(0) int mhi_init_chan_ctxt(struct mhi_controller *mhi_cntrl, struct mhi_chan *mhi_chan); void mhi_deinit_chan_ctxt(struct mhi_controller *mhi_cntrl, diff --git a/drivers/bus/mhi/host/main.c b/drivers/bus/mhi/host/main.c index 861551274319..53c0ffe30070 100644 --- a/drivers/bus/mhi/host/main.c +++ b/drivers/bus/mhi/host/main.c @@ -664,23 +664,6 @@ static int parse_xfer_event(struct mhi_controller *mhi_cntrl, mhi_cntrl->runtime_put(mhi_cntrl); } - /* - * Recycle the buffer if buffer is pre-allocated, - * if there is an error, not much we can do apart - * from dropping the packet - */ - if (mhi_chan->pre_alloc) { - if (mhi_queue_buf(mhi_chan->mhi_dev, - mhi_chan->dir, - buf_info->cb_buf, - buf_info->len, MHI_EOT)) { - dev_err(dev, - "Error recycling buffer for chan:%d\n", - mhi_chan->chan); - kfree(buf_info->cb_buf); - } - } - read_lock_bh(&mhi_chan->lock); } break; @@ -1177,17 +1160,12 @@ static int mhi_queue(struct mhi_device *mhi_dev, struct mhi_buf_info *buf_info, int mhi_queue_skb(struct mhi_device *mhi_dev, enum dma_data_direction dir, struct sk_buff *skb, size_t len, enum mhi_flags mflags) { - struct mhi_chan *mhi_chan = (dir == DMA_TO_DEVICE) ? mhi_dev->ul_chan : - mhi_dev->dl_chan; struct mhi_buf_info buf_info = { }; buf_info.v_addr = skb->data; buf_info.cb_buf = skb; buf_info.len = len; - if (unlikely(mhi_chan->pre_alloc)) - return -EINVAL; - return mhi_queue(mhi_dev, &buf_info, dir, mflags); } EXPORT_SYMBOL_GPL(mhi_queue_skb); @@ -1472,45 +1450,6 @@ static int mhi_prepare_channel(struct mhi_controller *mhi_cntrl, if (ret) goto error_pm_state; - if (mhi_chan->dir == DMA_FROM_DEVICE) - mhi_chan->pre_alloc = !!(flags & MHI_CH_INBOUND_ALLOC_BUFS); - - /* Pre-allocate buffer for xfer ring */ - if (mhi_chan->pre_alloc) { - int nr_el = get_nr_avail_ring_elements(mhi_cntrl, - &mhi_chan->tre_ring); - size_t len = mhi_cntrl->buffer_len; - - while (nr_el--) { - void *buf; - struct mhi_buf_info info = { }; - - buf = kmalloc(len, GFP_KERNEL); - if (!buf) { - ret = -ENOMEM; - goto error_pre_alloc; - } - - /* Prepare transfer descriptors */ - info.v_addr = buf; - info.cb_buf = buf; - info.len = len; - ret = mhi_gen_tre(mhi_cntrl, mhi_chan, &info, MHI_EOT); - if (ret) { - kfree(buf); - goto error_pre_alloc; - } - } - - read_lock_bh(&mhi_cntrl->pm_lock); - if (MHI_DB_ACCESS_VALID(mhi_cntrl)) { - read_lock_irq(&mhi_chan->lock); - mhi_ring_chan_db(mhi_cntrl, mhi_chan); - read_unlock_irq(&mhi_chan->lock); - } - read_unlock_bh(&mhi_cntrl->pm_lock); - } - mutex_unlock(&mhi_chan->mutex); return 0; @@ -1522,12 +1461,6 @@ error_pm_state: error_init_chan: mutex_unlock(&mhi_chan->mutex); - return ret; - -error_pre_alloc: - mutex_unlock(&mhi_chan->mutex); - mhi_unprepare_channel(mhi_cntrl, mhi_chan); - return ret; } @@ -1600,12 +1533,8 @@ static void mhi_reset_data_chan(struct mhi_controller *mhi_cntrl, mhi_del_ring_element(mhi_cntrl, buf_ring); mhi_del_ring_element(mhi_cntrl, tre_ring); - if (mhi_chan->pre_alloc) { - kfree(buf_info->cb_buf); - } else { - result.buf_addr = buf_info->cb_buf; - mhi_chan->xfer_cb(mhi_chan->mhi_dev, &result); - } + result.buf_addr = buf_info->cb_buf; + mhi_chan->xfer_cb(mhi_chan->mhi_dev, &result); } } @@ -1666,12 +1595,6 @@ int mhi_prepare_for_transfer(struct mhi_device *mhi_dev) } EXPORT_SYMBOL_GPL(mhi_prepare_for_transfer); -int mhi_prepare_for_transfer_autoqueue(struct mhi_device *mhi_dev) -{ - return __mhi_prepare_for_transfer(mhi_dev, MHI_CH_INBOUND_ALLOC_BUFS); -} -EXPORT_SYMBOL_GPL(mhi_prepare_for_transfer_autoqueue); - void mhi_unprepare_from_transfer(struct mhi_device *mhi_dev) { struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; diff --git a/include/linux/mhi.h b/include/linux/mhi.h index dd372b0123a6..88ccb3e14f48 100644 --- a/include/linux/mhi.h +++ b/include/linux/mhi.h @@ -215,7 +215,6 @@ enum mhi_db_brst_mode { * @lpm_notify: The channel master requires low power mode notifications * @offload_channel: The client manages the channel completely * @doorbell_mode_switch: Channel switches to doorbell mode on M0 transition - * @auto_queue: Framework will automatically queue buffers for DL traffic * @wake-capable: Channel capable of waking up the system */ struct mhi_channel_config { @@ -232,7 +231,6 @@ struct mhi_channel_config { bool lpm_notify; bool offload_channel; bool doorbell_mode_switch; - bool auto_queue; bool wake_capable; }; @@ -743,18 +741,6 @@ void mhi_device_put(struct mhi_device *mhi_dev); */ int mhi_prepare_for_transfer(struct mhi_device *mhi_dev); -/** - * mhi_prepare_for_transfer_autoqueue - Setup UL and DL channels with auto queue - * buffers for DL traffic - * @mhi_dev: Device associated with the channels - * - * Allocate and initialize the channel context and also issue the START channel - * command to both channels. Channels can be started only if both host and - * device execution environments match and channels are in a DISABLED state. - * The MHI core will automatically allocate and queue buffers for the DL traffic. - */ -int mhi_prepare_for_transfer_autoqueue(struct mhi_device *mhi_dev); - /** * mhi_unprepare_from_transfer - Reset UL and DL channels for data transfer. * Issue the RESET channel command and let the From 8535df5dd64ec02d85e65dbcf79a59db9c16d921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Thu, 18 Dec 2025 21:42:15 +0100 Subject: [PATCH 096/297] bus: mhi: host: Use bus callbacks for .probe() and .remove() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are nearly identical to the driver callbacks, the only relevant difference is that the bus remove method returns void (instead of an int where the value is ignored). The objective is to get rid of users of struct device_driver callbacks .probe(), and .remove() to eventually remove these. Signed-off-by: Uwe Kleine-König Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/e8032b3c2a8953a4a2b84dfa79a260c35f1d71b7.1766090211.git.ukleinek@kernel.org --- drivers/bus/mhi/host/init.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/bus/mhi/host/init.c b/drivers/bus/mhi/host/init.c index b020a6489c07..c47f66833cda 100644 --- a/drivers/bus/mhi/host/init.c +++ b/drivers/bus/mhi/host/init.c @@ -1255,7 +1255,7 @@ struct mhi_device *mhi_alloc_device(struct mhi_controller *mhi_cntrl) return mhi_dev; } -static int mhi_driver_probe(struct device *dev) +static int mhi_probe(struct device *dev) { struct mhi_device *mhi_dev = to_mhi_device(dev); struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; @@ -1331,7 +1331,7 @@ exit_probe: return ret; } -static int mhi_driver_remove(struct device *dev) +static void mhi_remove(struct device *dev) { struct mhi_device *mhi_dev = to_mhi_device(dev); struct mhi_driver *mhi_drv = to_mhi_driver(dev->driver); @@ -1345,7 +1345,7 @@ static int mhi_driver_remove(struct device *dev) /* Skip if it is a controller device */ if (mhi_dev->dev_type == MHI_DEVICE_CONTROLLER) - return 0; + return; /* Reset both channels */ for (dir = 0; dir < 2; dir++) { @@ -1397,8 +1397,6 @@ static int mhi_driver_remove(struct device *dev) while (mhi_dev->dev_wake) mhi_device_put(mhi_dev); - - return 0; } int __mhi_driver_register(struct mhi_driver *mhi_drv, struct module *owner) @@ -1410,8 +1408,6 @@ int __mhi_driver_register(struct mhi_driver *mhi_drv, struct module *owner) driver->bus = &mhi_bus_type; driver->owner = owner; - driver->probe = mhi_driver_probe; - driver->remove = mhi_driver_remove; return driver_register(driver); } @@ -1458,6 +1454,8 @@ const struct bus_type mhi_bus_type = { .dev_name = "mhi", .match = mhi_match, .uevent = mhi_uevent, + .probe = mhi_probe, + .remove = mhi_remove, .dev_groups = mhi_dev_groups, }; From 91a0b0dce350766675961892ba4431363c4e29f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Thu, 18 Dec 2025 21:42:16 +0100 Subject: [PATCH 097/297] bus: mhi: ep: Use bus callbacks for .probe() and .remove() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are nearly identical to the driver callbacks, the only relevant difference is that the bus remove method returns void (instead of an int where the value is ignored). The objective is to get rid of users of struct device_driver callbacks .probe(), and .remove() to eventually remove these. Signed-off-by: Uwe Kleine-König Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/2a79d99182a5171e83a07bf9f438ae31362f7e5d.1766090211.git.ukleinek@kernel.org --- drivers/bus/mhi/ep/main.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/bus/mhi/ep/main.c b/drivers/bus/mhi/ep/main.c index 3c208b5c8446..35922e7a24c1 100644 --- a/drivers/bus/mhi/ep/main.c +++ b/drivers/bus/mhi/ep/main.c @@ -1596,7 +1596,7 @@ void mhi_ep_unregister_controller(struct mhi_ep_cntrl *mhi_cntrl) } EXPORT_SYMBOL_GPL(mhi_ep_unregister_controller); -static int mhi_ep_driver_probe(struct device *dev) +static int mhi_ep_probe(struct device *dev) { struct mhi_ep_device *mhi_dev = to_mhi_ep_device(dev); struct mhi_ep_driver *mhi_drv = to_mhi_ep_driver(dev->driver); @@ -1609,7 +1609,7 @@ static int mhi_ep_driver_probe(struct device *dev) return mhi_drv->probe(mhi_dev, mhi_dev->id); } -static int mhi_ep_driver_remove(struct device *dev) +static void mhi_ep_remove(struct device *dev) { struct mhi_ep_device *mhi_dev = to_mhi_ep_device(dev); struct mhi_ep_driver *mhi_drv = to_mhi_ep_driver(dev->driver); @@ -1619,7 +1619,7 @@ static int mhi_ep_driver_remove(struct device *dev) /* Skip if it is a controller device */ if (mhi_dev->dev_type == MHI_DEVICE_CONTROLLER) - return 0; + return; /* Disconnect the channels associated with the driver */ for (dir = 0; dir < 2; dir++) { @@ -1643,8 +1643,6 @@ static int mhi_ep_driver_remove(struct device *dev) /* Remove the client driver now */ mhi_drv->remove(mhi_dev); - - return 0; } int __mhi_ep_driver_register(struct mhi_ep_driver *mhi_drv, struct module *owner) @@ -1660,8 +1658,6 @@ int __mhi_ep_driver_register(struct mhi_ep_driver *mhi_drv, struct module *owner driver->bus = &mhi_ep_bus_type; driver->owner = owner; - driver->probe = mhi_ep_driver_probe; - driver->remove = mhi_ep_driver_remove; return driver_register(driver); } @@ -1708,6 +1704,8 @@ const struct bus_type mhi_ep_bus_type = { .dev_name = "mhi_ep", .match = mhi_ep_match, .uevent = mhi_ep_uevent, + .probe = mhi_ep_probe, + .remove = mhi_ep_remove, }; static int __init mhi_ep_init(void) From bf394cc8036989c9cc7d82ddede4c57e24912dc4 Mon Sep 17 00:00:00 2001 From: Ariana Lazar Date: Tue, 16 Dec 2025 14:05:51 +0200 Subject: [PATCH 098/297] iio: dac: adding support for Microchip MCP47FEB02 This is the iio driver for Microchip MCP47F(E/V)B(0/1/2)1, MCP47F(E/V)B(0/1/2)2, MCP47F(E/V)B(0/1/2)4 and MCP47F(E/V)B(0/1/2)8 series of buffered voltage output Digital-to-Analog Converters with nonvolatile or volatile memory and an I2C Interface. The families support up to 8 output channels. The devices can be 8-bit, 10-bit and 12-bit. Signed-off-by: Ariana Lazar Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/dac/Kconfig | 20 + drivers/iio/dac/Makefile | 1 + drivers/iio/dac/mcp47feb02.c | 1250 ++++++++++++++++++++++++++++++++++ 4 files changed, 1272 insertions(+) create mode 100644 drivers/iio/dac/mcp47feb02.c diff --git a/MAINTAINERS b/MAINTAINERS index 3a965c4f7798..9a42d20144a4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15660,6 +15660,7 @@ M: Ariana Lazar L: linux-iio@vger.kernel.org S: Supported F: Documentation/devicetree/bindings/iio/dac/microchip,mcp47feb02.yaml +F: drivers/iio/dac/mcp47feb02.c MCP4821 DAC DRIVER M: Anshul Dalal diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index 7cd3caec1262..e47d73300b07 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -524,6 +524,26 @@ config MCP4728 To compile this driver as a module, choose M here: the module will be called mcp4728. +config MCP47FEB02 + tristate "MCP47F(E/V)B01/02/04/08/11/12/14/18/21/22/24/28 DAC driver" + depends on I2C + help + Say yes here if you want to build the driver for the Microchip: + - 8-bit DAC: + MCP47FEB01, MCP47FEB02, MCP47FEB04, MCP47FEB08, + MCP47FVB01, MCP47FVB02, MCP47FVB04, MCP47FVB08 + - 10-bit DAC: + MCP47FEB11, MCP47FEB12, MCP47FEB14, MCP47FEB18, + MCP47FVB11, MCP47FVB12, MCP47FVB14, MCP47FVB18 + - 12-bit DAC: + MCP47FEB21, MCP47FEB22, MCP47FEB24, MCP47FEB28, + MCP47FVB21, MCP47FVB22, MCP47FVB24, MCP47FVB28 + having 1 to 8 channels, 8/10/12-bit digital-to-analog converter + (DAC) with I2C interface. + + To compile this driver as a module, choose M here: the module + will be called mcp47feb02. + config MCP4821 tristate "MCP4801/02/11/12/21/22 DAC driver" depends on SPI diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index e6ac4c67e337..9443e3c71c79 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -52,6 +52,7 @@ obj-$(CONFIG_MAX5522) += max5522.o obj-$(CONFIG_MAX5821) += max5821.o obj-$(CONFIG_MCP4725) += mcp4725.o obj-$(CONFIG_MCP4728) += mcp4728.o +obj-$(CONFIG_MCP47FEB02) += mcp47feb02.o obj-$(CONFIG_MCP4821) += mcp4821.o obj-$(CONFIG_MCP4922) += mcp4922.o obj-$(CONFIG_STM32_DAC_CORE) += stm32-dac-core.o diff --git a/drivers/iio/dac/mcp47feb02.c b/drivers/iio/dac/mcp47feb02.c new file mode 100644 index 000000000000..b218f0c3a0bd --- /dev/null +++ b/drivers/iio/dac/mcp47feb02.c @@ -0,0 +1,1250 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface + * + * Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries + * + * Author: Ariana Lazar + * + * Datasheet links: + * [MCP47FEBxx] https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005375A.pdf + * [MCP47FVBxx] https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005405A.pdf + * [MCP47FxBx4/8] https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP47FXBX48-Data-Sheet-DS200006368A.pdf + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register addresses must be left shifted with 3 positions in order to append command mask */ +#define MCP47FEB02_DAC0_REG_ADDR 0x00 +#define MCP47FEB02_VREF_REG_ADDR 0x40 +#define MCP47FEB02_POWER_DOWN_REG_ADDR 0x48 +#define MCP47FEB02_DAC_CTRL_MASK GENMASK(1, 0) + +#define MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR 0x50 +#define MCP47FEB02_GAIN_BIT_MASK BIT(0) +#define MCP47FEB02_GAIN_BIT_STATUS_EEWA_MASK BIT(6) +#define MCP47FEB02_GAIN_BITS_MASK GENMASK(15, 8) + +#define MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR 0x58 + +#define MCP47FEB02_NV_DAC0_REG_ADDR 0x80 +#define MCP47FEB02_NV_VREF_REG_ADDR 0xC0 +#define MCP47FEB02_NV_POWER_DOWN_REG_ADDR 0xC8 +#define MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR 0xD0 +#define MCP47FEB02_NV_I2C_SLAVE_ADDR_MASK GENMASK(7, 0) + +/* Voltage reference, Power-Down control register and DAC Wiperlock status register fields */ +#define DAC_CTRL_MASK(ch) (GENMASK(1, 0) << (2 * (ch))) +#define DAC_CTRL_VAL(ch, val) ((val) << (2 * (ch))) + +/* Gain Control and I2C Slave Address Reguster fields */ +#define DAC_GAIN_MASK(ch) (BIT(0) << (8 + (ch))) +#define DAC_GAIN_VAL(ch, val) ((val) << (8 + (ch))) + +#define REG_ADDR(reg) ((reg) << 3) +#define NV_REG_ADDR(reg) ((NV_DAC_ADDR_OFFSET + (reg)) << 3) +#define READFLAG_MASK GENMASK(2, 1) + +#define MCP47FEB02_MAX_CH 8 +#define MCP47FEB02_MAX_SCALES_CH 3 +#define MCP47FEB02_DAC_WIPER_UNLOCKED 0 +#define MCP47FEB02_NORMAL_OPERATION 0 +#define MCP47FEB02_INTERNAL_BAND_GAP_mV 2440 +#define NV_DAC_ADDR_OFFSET 0x10 + +enum mcp47feb02_vref_mode { + MCP47FEB02_VREF_VDD = 0, + MCP47FEB02_INTERNAL_BAND_GAP = 1, + MCP47FEB02_EXTERNAL_VREF_UNBUFFERED = 2, + MCP47FEB02_EXTERNAL_VREF_BUFFERED = 3, +}; + +enum mcp47feb02_scale { + MCP47FEB02_SCALE_VDD = 0, + MCP47FEB02_SCALE_GAIN_X1 = 1, + MCP47FEB02_SCALE_GAIN_X2 = 2, +}; + +enum mcp47feb02_gain_bit_mode { + MCP47FEB02_GAIN_BIT_X1 = 0, + MCP47FEB02_GAIN_BIT_X2 = 1, +}; + +static const char * const mcp47feb02_powerdown_modes[] = { + "1kohm_to_gnd", + "100kohm_to_gnd", + "open_circuit", +}; + +/** + * struct mcp47feb02_features - chip specific data + * @name: device name + * @phys_channels: number of hardware channels + * @resolution: DAC resolution + * @have_ext_vref1: does the hardware have an the second external voltage reference? + * @have_eeprom: does the hardware have an internal eeprom? + */ +struct mcp47feb02_features { + const char *name; + unsigned int phys_channels; + unsigned int resolution; + bool have_ext_vref1; + bool have_eeprom; +}; + +static const struct mcp47feb02_features mcp47feb01_chip_features = { + .name = "mcp47feb01", + .phys_channels = 1, + .resolution = 8, + .have_ext_vref1 = false, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb02_chip_features = { + .name = "mcp47feb02", + .phys_channels = 2, + .resolution = 8, + .have_ext_vref1 = false, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb04_chip_features = { + .name = "mcp47feb04", + .phys_channels = 4, + .resolution = 8, + .have_ext_vref1 = true, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb08_chip_features = { + .name = "mcp47feb08", + .phys_channels = 8, + .resolution = 8, + .have_ext_vref1 = true, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb11_chip_features = { + .name = "mcp47feb11", + .phys_channels = 1, + .resolution = 10, + .have_ext_vref1 = false, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb12_chip_features = { + .name = "mcp47feb12", + .phys_channels = 2, + .resolution = 10, + .have_ext_vref1 = false, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb14_chip_features = { + .name = "mcp47feb14", + .phys_channels = 4, + .resolution = 10, + .have_ext_vref1 = true, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb18_chip_features = { + .name = "mcp47feb18", + .phys_channels = 8, + .resolution = 10, + .have_ext_vref1 = true, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb21_chip_features = { + .name = "mcp47feb21", + .phys_channels = 1, + .resolution = 12, + .have_ext_vref1 = false, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb22_chip_features = { + .name = "mcp47feb22", + .phys_channels = 2, + .resolution = 12, + .have_ext_vref1 = false, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb24_chip_features = { + .name = "mcp47feb24", + .phys_channels = 4, + .resolution = 12, + .have_ext_vref1 = true, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb28_chip_features = { + .name = "mcp47feb28", + .phys_channels = 8, + .resolution = 12, + .have_ext_vref1 = true, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47fvb01_chip_features = { + .name = "mcp47fvb01", + .phys_channels = 1, + .resolution = 8, + .have_ext_vref1 = false, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb02_chip_features = { + .name = "mcp47fvb02", + .phys_channels = 2, + .resolution = 8, + .have_ext_vref1 = false, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb04_chip_features = { + .name = "mcp47fvb04", + .phys_channels = 4, + .resolution = 8, + .have_ext_vref1 = true, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb08_chip_features = { + .name = "mcp47fvb08", + .phys_channels = 8, + .resolution = 8, + .have_ext_vref1 = true, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb11_chip_features = { + .name = "mcp47fvb11", + .phys_channels = 1, + .resolution = 10, + .have_ext_vref1 = false, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb12_chip_features = { + .name = "mcp47fvb12", + .phys_channels = 2, + .resolution = 10, + .have_ext_vref1 = false, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb14_chip_features = { + .name = "mcp47fvb14", + .phys_channels = 4, + .resolution = 10, + .have_ext_vref1 = true, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb18_chip_features = { + .name = "mcp47fvb18", + .phys_channels = 8, + .resolution = 10, + .have_ext_vref1 = true, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb21_chip_features = { + .name = "mcp47fvb21", + .phys_channels = 1, + .resolution = 12, + .have_ext_vref1 = false, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb22_chip_features = { + .name = "mcp47fvb22", + .phys_channels = 2, + .resolution = 12, + .have_ext_vref1 = false, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb24_chip_features = { + .name = "mcp47fvb24", + .phys_channels = 4, + .resolution = 12, + .have_ext_vref1 = true, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb28_chip_features = { + .name = "mcp47fvb28", + .phys_channels = 8, + .resolution = 12, + .have_ext_vref1 = true, + .have_eeprom = false, +}; + +/** + * struct mcp47feb02_channel_data - channel configuration + * @ref_mode: chosen voltage for reference + * @use_2x_gain: output driver gain control + * @powerdown: is false if the channel is in normal operation mode + * @powerdown_mode: selected power-down mode + * @dac_data: dac value + */ +struct mcp47feb02_channel_data { + u8 ref_mode; + bool use_2x_gain; + bool powerdown; + u8 powerdown_mode; + u16 dac_data; +}; + +/** + * struct mcp47feb02_data - chip configuration + * @chdata: options configured for each channel on the device + * @lock: prevents concurrent reads/writes to driver's state members + * @chip_features: pointer to features struct + * @scale_1: scales set on channels that are based on Vref1 + * @scale: scales set on channels that are based on Vref/Vref0 + * @active_channels_mask: enabled channels + * @regmap: regmap for directly accessing device register + * @labels: table with channels labels + * @phys_channels: physical channels on the device + * @vref1_buffered: Vref1 buffer is enabled + * @vref_buffered: Vref/Vref0 buffer is enabled + * @use_vref1: vref1-supply is defined + * @use_vref: vref-supply is defined + */ +struct mcp47feb02_data { + struct mcp47feb02_channel_data chdata[MCP47FEB02_MAX_CH]; + struct mutex lock; /* prevents concurrent reads/writes to driver's state members */ + const struct mcp47feb02_features *chip_features; + int scale_1[2 * MCP47FEB02_MAX_SCALES_CH]; + int scale[2 * MCP47FEB02_MAX_SCALES_CH]; + unsigned long active_channels_mask; + struct regmap *regmap; + const char *labels[MCP47FEB02_MAX_CH]; + u16 phys_channels; + bool vref1_buffered; + bool vref_buffered; + bool use_vref1; + bool use_vref; +}; + +static const struct regmap_range mcp47feb02_readable_ranges[] = { + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), + regmap_reg_range(MCP47FEB02_NV_DAC0_REG_ADDR, MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR), +}; + +static const struct regmap_range mcp47feb02_writable_ranges[] = { + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), + regmap_reg_range(MCP47FEB02_NV_DAC0_REG_ADDR, MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR), +}; + +static const struct regmap_range mcp47feb02_volatile_ranges[] = { + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), + regmap_reg_range(MCP47FEB02_NV_DAC0_REG_ADDR, MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR), + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), + regmap_reg_range(MCP47FEB02_NV_DAC0_REG_ADDR, MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR), +}; + +static const struct regmap_access_table mcp47feb02_readable_table = { + .yes_ranges = mcp47feb02_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp47feb02_readable_ranges), +}; + +static const struct regmap_access_table mcp47feb02_writable_table = { + .yes_ranges = mcp47feb02_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp47feb02_writable_ranges), +}; + +static const struct regmap_access_table mcp47feb02_volatile_table = { + .yes_ranges = mcp47feb02_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp47feb02_volatile_ranges), +}; + +static const struct regmap_config mcp47feb02_regmap_config = { + .name = "mcp47feb02_regmap", + .reg_bits = 8, + .val_bits = 16, + .rd_table = &mcp47feb02_readable_table, + .wr_table = &mcp47feb02_writable_table, + .volatile_table = &mcp47feb02_volatile_table, + .max_register = MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR, + .read_flag_mask = READFLAG_MASK, + .cache_type = REGCACHE_MAPLE, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; + +/* For devices that doesn't have nonvolatile memory */ +static const struct regmap_range mcp47fvb02_readable_ranges[] = { + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), +}; + +static const struct regmap_range mcp47fvb02_writable_ranges[] = { + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), +}; + +static const struct regmap_range mcp47fvb02_volatile_ranges[] = { + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), +}; + +static const struct regmap_access_table mcp47fvb02_readable_table = { + .yes_ranges = mcp47fvb02_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp47fvb02_readable_ranges), +}; + +static const struct regmap_access_table mcp47fvb02_writable_table = { + .yes_ranges = mcp47fvb02_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp47fvb02_writable_ranges), +}; + +static const struct regmap_access_table mcp47fvb02_volatile_table = { + .yes_ranges = mcp47fvb02_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp47fvb02_volatile_ranges), +}; + +static const struct regmap_config mcp47fvb02_regmap_config = { + .name = "mcp47fvb02_regmap", + .reg_bits = 8, + .val_bits = 16, + .rd_table = &mcp47fvb02_readable_table, + .wr_table = &mcp47fvb02_writable_table, + .volatile_table = &mcp47fvb02_volatile_table, + .max_register = MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR, + .read_flag_mask = READFLAG_MASK, + .cache_type = REGCACHE_MAPLE, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; + +static int mcp47feb02_write_to_eeprom(struct mcp47feb02_data *data, unsigned int reg, + unsigned int val) +{ + int eewa_val, ret; + + /* + * Wait until the currently occurring EEPROM Write Cycle is completed. + * Only serial commands to the volatile memory are allowed. + */ + guard(mutex)(&data->lock); + + ret = regmap_read_poll_timeout(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, + eewa_val, + !(eewa_val & MCP47FEB02_GAIN_BIT_STATUS_EEWA_MASK), + USEC_PER_MSEC, USEC_PER_MSEC * 5); + if (ret) + return ret; + + return regmap_write(data->regmap, reg, val); +} + +static ssize_t store_eeprom_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct mcp47feb02_data *data = iio_priv(dev_to_iio_dev(dev)); + unsigned int i, val, val1, eewa_val; + bool state; + int ret; + + ret = kstrtobool(buf, &state); + if (ret) + return ret; + + if (!state) + return 0; + + /* + * Verify DAC Wiper and DAC Configuration are unlocked. If both are disabled, + * writing to EEPROM is available. + */ + ret = regmap_read(data->regmap, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR, &val); + if (ret) + return ret; + + if (val) { + dev_err(dev, "DAC Wiper and DAC Configuration not are unlocked.\n"); + return -EINVAL; + } + + for_each_set_bit(i, &data->active_channels_mask, data->phys_channels) { + ret = mcp47feb02_write_to_eeprom(data, NV_REG_ADDR(i), + data->chdata[i].dac_data); + if (ret) + return ret; + } + + ret = regmap_read(data->regmap, MCP47FEB02_VREF_REG_ADDR, &val); + if (ret) + return ret; + + ret = mcp47feb02_write_to_eeprom(data, MCP47FEB02_NV_VREF_REG_ADDR, val); + if (ret) + return ret; + + ret = regmap_read(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, &val); + if (ret) + return ret; + + ret = mcp47feb02_write_to_eeprom(data, MCP47FEB02_NV_POWER_DOWN_REG_ADDR, val); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, eewa_val, + !(eewa_val & MCP47FEB02_GAIN_BIT_STATUS_EEWA_MASK), + USEC_PER_MSEC, USEC_PER_MSEC * 5); + if (ret) + return ret; + + ret = regmap_read(data->regmap, MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR, &val); + if (ret) + return ret; + + ret = regmap_read(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, &val1); + if (ret) + return ret; + + ret = mcp47feb02_write_to_eeprom(data, MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR, + (val1 & MCP47FEB02_GAIN_BITS_MASK) | + (val & MCP47FEB02_NV_I2C_SLAVE_ADDR_MASK)); + if (ret) + return ret; + + return len; +} + +static IIO_DEVICE_ATTR_WO(store_eeprom, 0); + +static struct attribute *mcp47feb02_attributes[] = { + &iio_dev_attr_store_eeprom.dev_attr.attr, + NULL +}; + +static const struct attribute_group mcp47feb02_attribute_group = { + .attrs = mcp47feb02_attributes, +}; + +static int mcp47feb02_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct mcp47feb02_data *data = iio_priv(indio_dev); + int ret; + u8 ch; + + guard(mutex)(&data->lock); + + for_each_set_bit(ch, &data->active_channels_mask, data->phys_channels) { + u8 pd_mode; + + data->chdata[ch].powerdown = true; + pd_mode = data->chdata[ch].powerdown_mode + 1; + ret = regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, + DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, pd_mode)); + if (ret) + return ret; + + ret = regmap_write(data->regmap, REG_ADDR(ch), data->chdata[ch].dac_data); + if (ret) + return ret; + } + + return 0; +} + +static int mcp47feb02_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct mcp47feb02_data *data = iio_priv(indio_dev); + u8 ch; + + guard(mutex)(&data->lock); + + for_each_set_bit(ch, &data->active_channels_mask, data->phys_channels) { + u8 pd_mode; + int ret; + + data->chdata[ch].powerdown = false; + pd_mode = data->chdata[ch].powerdown_mode + 1; + + ret = regmap_write(data->regmap, REG_ADDR(ch), data->chdata[ch].dac_data); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, MCP47FEB02_VREF_REG_ADDR, + DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, pd_mode)); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, + DAC_GAIN_MASK(ch), + DAC_GAIN_VAL(ch, data->chdata[ch].use_2x_gain)); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, + DAC_CTRL_MASK(ch), + DAC_CTRL_VAL(ch, MCP47FEB02_NORMAL_OPERATION)); + if (ret) + return ret; + } + + return 0; +} + +static int mcp47feb02_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + + return data->chdata[chan->address].powerdown_mode; +} + +static int mcp47feb02_set_powerdown_mode(struct iio_dev *indio_dev, const struct iio_chan_spec *ch, + unsigned int mode) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + + data->chdata[ch->address].powerdown_mode = mode; + + return 0; +} + +static ssize_t mcp47feb02_read_powerdown(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *ch, char *buf) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + + /* Print if channel is in a power-down mode or not */ + return sysfs_emit(buf, "%d\n", data->chdata[ch->address].powerdown); +} + +static ssize_t mcp47feb02_write_powerdown(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *ch, const char *buf, + size_t len) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + u32 reg = ch->address; + u8 tmp_pd_mode; + bool state; + int ret; + + guard(mutex)(&data->lock); + + ret = kstrtobool(buf, &state); + if (ret) + return ret; + + /* + * Set the channel to the specified power-down mode. Exiting power-down mode + * requires writing normal operation mode (0) to the channel-specific register bits. + */ + tmp_pd_mode = state ? (data->chdata[reg].powerdown_mode + 1) : MCP47FEB02_NORMAL_OPERATION; + ret = regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, + DAC_CTRL_MASK(reg), DAC_CTRL_VAL(reg, tmp_pd_mode)); + if (ret) + return ret; + + data->chdata[reg].powerdown = state; + + return len; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mcp47feb02_pm_ops, mcp47feb02_suspend, mcp47feb02_resume); + +static const struct iio_enum mcp47febxx_powerdown_mode_enum = { + .items = mcp47feb02_powerdown_modes, + .num_items = ARRAY_SIZE(mcp47feb02_powerdown_modes), + .get = mcp47feb02_get_powerdown_mode, + .set = mcp47feb02_set_powerdown_mode, +}; + +static const struct iio_chan_spec_ext_info mcp47feb02_ext_info[] = { + { + .name = "powerdown", + .read = mcp47feb02_read_powerdown, + .write = mcp47feb02_write_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &mcp47febxx_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, &mcp47febxx_powerdown_mode_enum), + { } +}; + +static const struct iio_chan_spec mcp47febxx_ch_template = { + .type = IIO_VOLTAGE, + .output = 1, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), + .ext_info = mcp47feb02_ext_info, +}; + +static void mcp47feb02_init_scale(struct mcp47feb02_data *data, enum mcp47feb02_scale scale, + int vref_mV, int scale_avail[]) +{ + u32 value_micro, value_int; + u64 tmp; + + /* vref_mV should not be negative */ + tmp = (u64)vref_mV * MICRO >> data->chip_features->resolution; + value_int = div_u64_rem(tmp, MICRO, &value_micro); + scale_avail[scale * 2] = value_int; + scale_avail[scale * 2 + 1] = value_micro; +} + +static int mcp47feb02_init_scales_avail(struct mcp47feb02_data *data, int vdd_mV, + int vref_mV, int vref1_mV) +{ + struct device *dev = regmap_get_device(data->regmap); + int tmp_vref; + + mcp47feb02_init_scale(data, MCP47FEB02_SCALE_VDD, vdd_mV, data->scale); + + if (data->use_vref) + tmp_vref = vref_mV; + else + tmp_vref = MCP47FEB02_INTERNAL_BAND_GAP_mV; + + mcp47feb02_init_scale(data, MCP47FEB02_SCALE_GAIN_X1, tmp_vref, data->scale); + mcp47feb02_init_scale(data, MCP47FEB02_SCALE_GAIN_X2, tmp_vref * 2, data->scale); + + if (data->phys_channels >= 4) { + mcp47feb02_init_scale(data, MCP47FEB02_SCALE_VDD, vdd_mV, data->scale_1); + + if (data->use_vref1 && vref1_mV <= 0) + return dev_err_probe(dev, vref1_mV, "Invalid voltage for Vref1\n"); + + if (data->use_vref1) + tmp_vref = vref1_mV; + else + tmp_vref = MCP47FEB02_INTERNAL_BAND_GAP_mV; + + mcp47feb02_init_scale(data, MCP47FEB02_SCALE_GAIN_X1, + tmp_vref, data->scale_1); + mcp47feb02_init_scale(data, MCP47FEB02_SCALE_GAIN_X2, + tmp_vref * 2, data->scale_1); + } + + return 0; +} + +static int mcp47feb02_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *ch, + const int **vals, int *type, int *length, long info) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SCALE: + switch (ch->type) { + case IIO_VOLTAGE: + if (data->phys_channels >= 4 && (ch->address % 2)) + *vals = data->scale_1; + else + *vals = data->scale; + + *length = 2 * MCP47FEB02_MAX_SCALES_CH; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static void mcp47feb02_get_scale(int ch, struct mcp47feb02_data *data, int *val, int *val2) +{ + enum mcp47feb02_scale current_scale; + + if (data->chdata[ch].ref_mode == MCP47FEB02_VREF_VDD) + current_scale = MCP47FEB02_SCALE_VDD; + else if (data->chdata[ch].use_2x_gain) + current_scale = MCP47FEB02_SCALE_GAIN_X2; + else + current_scale = MCP47FEB02_SCALE_GAIN_X1; + + if (data->phys_channels >= 4 && (ch % 2)) { + *val = data->scale_1[current_scale * 2]; + *val2 = data->scale_1[current_scale * 2 + 1]; + } else { + *val = data->scale[current_scale * 2]; + *val2 = data->scale[current_scale * 2 + 1]; + } +} + +static int mcp47feb02_check_scale(struct mcp47feb02_data *data, int val, int val2, int scale[]) +{ + unsigned int i; + + for (i = 0; i < MCP47FEB02_MAX_SCALES_CH; i++) { + if (scale[i * 2] == val && scale[i * 2 + 1] == val2) + return i; + } + + return -EINVAL; +} + +static int mcp47feb02_ch_scale(struct mcp47feb02_data *data, int ch, int scale) +{ + int tmp_val, ret; + + if (scale == MCP47FEB02_SCALE_VDD) { + tmp_val = MCP47FEB02_VREF_VDD; + } else if (data->phys_channels >= 4 && (ch % 2)) { + if (data->use_vref1) { + if (data->vref1_buffered) + tmp_val = MCP47FEB02_EXTERNAL_VREF_BUFFERED; + else + tmp_val = MCP47FEB02_EXTERNAL_VREF_UNBUFFERED; + } else { + tmp_val = MCP47FEB02_INTERNAL_BAND_GAP; + } + } else if (data->use_vref) { + if (data->vref_buffered) + tmp_val = MCP47FEB02_EXTERNAL_VREF_BUFFERED; + else + tmp_val = MCP47FEB02_EXTERNAL_VREF_UNBUFFERED; + } else { + tmp_val = MCP47FEB02_INTERNAL_BAND_GAP; + } + + ret = regmap_update_bits(data->regmap, MCP47FEB02_VREF_REG_ADDR, + DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, tmp_val)); + if (ret) + return ret; + + data->chdata[ch].ref_mode = tmp_val; + + return 0; +} + +/* + * Setting the scale in order to choose between VDD and (Vref or Band Gap) from the user + * space. The VREF pin is either an input or an output, therefore the user cannot + * simultaneously connect an external voltage reference to the pin and select the + * internal Band Gap. + * When the DAC’s voltage reference is configured as the VREF pin, the pin is an input. + * When the DAC’s voltage reference is configured as the internal Band Gap, + * the VREF pin is an output. + * If Vref/Vref1 voltage is not available, then the internal Band Gap will be used + * to calculate the values for the scale. + */ +static int mcp47feb02_set_scale(struct mcp47feb02_data *data, int ch, int scale) +{ + int tmp_val, ret; + + ret = mcp47feb02_ch_scale(data, ch, scale); + if (ret) + return ret; + + if (scale == MCP47FEB02_SCALE_GAIN_X2) + tmp_val = MCP47FEB02_GAIN_BIT_X2; + else + tmp_val = MCP47FEB02_GAIN_BIT_X1; + + ret = regmap_update_bits(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, + DAC_GAIN_MASK(ch), DAC_GAIN_VAL(ch, tmp_val)); + if (ret) + return ret; + + data->chdata[ch].use_2x_gain = tmp_val; + + return 0; +} + +static int mcp47feb02_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *ch, + int *val, int *val2, long mask) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(data->regmap, REG_ADDR(ch->address), val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + mcp47feb02_get_scale(ch->address, data, val, val2); + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int mcp47feb02_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *ch, + int val, int val2, long mask) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + int *tmp_scale, ret; + + guard(mutex)(&data->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_write(data->regmap, REG_ADDR(ch->address), val); + if (ret) + return ret; + + data->chdata[ch->address].dac_data = val; + return 0; + case IIO_CHAN_INFO_SCALE: + if (data->phys_channels >= 4 && (ch->address % 2)) + tmp_scale = data->scale_1; + else + tmp_scale = data->scale; + + ret = mcp47feb02_check_scale(data, val, val2, tmp_scale); + if (ret < 0) + return ret; + + return mcp47feb02_set_scale(data, ch->address, ret); + default: + return -EINVAL; + } +} + +static int mcp47feb02_read_label(struct iio_dev *indio_dev, struct iio_chan_spec const *ch, + char *label) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + + return sysfs_emit(label, "%s\n", data->labels[ch->address]); +} + +static const struct iio_info mcp47feb02_info = { + .read_raw = mcp47feb02_read_raw, + .write_raw = mcp47feb02_write_raw, + .read_label = mcp47feb02_read_label, + .read_avail = &mcp47feb02_read_avail, + .attrs = &mcp47feb02_attribute_group, +}; + +static const struct iio_info mcp47fvb02_info = { + .read_raw = mcp47feb02_read_raw, + .write_raw = mcp47feb02_write_raw, + .read_label = mcp47feb02_read_label, + .read_avail = &mcp47feb02_read_avail, +}; + +static int mcp47feb02_parse_fw(struct iio_dev *indio_dev, + const struct mcp47feb02_features *chip_features) +{ + struct iio_chan_spec chanspec = mcp47febxx_ch_template; + struct mcp47feb02_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + struct iio_chan_spec *channels; + u32 num_channels; + u8 chan_idx = 0; + + guard(mutex)(&data->lock); + + num_channels = device_get_child_node_count(dev); + if (num_channels > chip_features->phys_channels) + return dev_err_probe(dev, -EINVAL, "More channels than the chip supports\n"); + + if (!num_channels) + return dev_err_probe(dev, -EINVAL, "No channel specified in the devicetree.\n"); + + channels = devm_kcalloc(dev, num_channels, sizeof(*channels), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + device_for_each_child_node_scoped(dev, child) { + u32 reg = 0; + int ret; + + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + return dev_err_probe(dev, ret, "Invalid channel number\n"); + + if (reg >= chip_features->phys_channels) + return dev_err_probe(dev, -EINVAL, + "The index of the channels does not match the chip\n"); + + set_bit(reg, &data->active_channels_mask); + + ret = fwnode_property_read_string(child, "label", &data->labels[reg]); + if (ret) + return dev_err_probe(dev, ret, "%pfw: invalid label\n", + fwnode_get_name(child)); + + chanspec.address = reg; + chanspec.channel = reg; + channels[chan_idx] = chanspec; + chan_idx++; + } + + indio_dev->num_channels = num_channels; + indio_dev->channels = channels; + indio_dev->modes = INDIO_DIRECT_MODE; + data->phys_channels = chip_features->phys_channels; + + data->vref_buffered = device_property_read_bool(dev, "microchip,vref-buffered"); + + if (chip_features->have_ext_vref1) + data->vref1_buffered = device_property_read_bool(dev, "microchip,vref1-buffered"); + + return 0; +} + +static int mcp47feb02_init_ctrl_regs(struct mcp47feb02_data *data) +{ + unsigned int i, vref_ch, gain_ch, pd_ch; + int ret; + + ret = regmap_read(data->regmap, MCP47FEB02_VREF_REG_ADDR, &vref_ch); + if (ret) + return ret; + + ret = regmap_read(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, &gain_ch); + if (ret) + return ret; + + ret = regmap_read(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, &pd_ch); + if (ret) + return ret; + + gain_ch = gain_ch & MCP47FEB02_GAIN_BITS_MASK; + for_each_set_bit(i, &data->active_channels_mask, data->phys_channels) { + struct device *dev = regmap_get_device(data->regmap); + unsigned int pd_tmp; + + data->chdata[i].ref_mode = (vref_ch >> (2 * i)) & MCP47FEB02_DAC_CTRL_MASK; + data->chdata[i].use_2x_gain = (gain_ch >> i) & MCP47FEB02_GAIN_BIT_MASK; + + /* + * Inform the user that the current voltage reference read from the volatile + * register of the chip is different from the one specified in the device tree. + * Considering that the user cannot have an external voltage reference connected + * to the pin and select the internal Band Gap at the same time, in order to avoid + * miscofiguring the reference voltage, the volatile register will not be written. + * In order to overwrite the setting from volatile register with the one from the + * device tree, the user needs to write the chosen scale. + */ + switch (data->chdata[i].ref_mode) { + case MCP47FEB02_INTERNAL_BAND_GAP: + if (data->phys_channels >= 4 && (i % 2) && data->use_vref1) { + dev_dbg(dev, "ch[%u]: was configured to use internal band gap", i); + dev_dbg(dev, "ch[%u]: reference voltage set to VREF1", i); + break; + } + if ((data->phys_channels < 4 || (data->phys_channels >= 4 && !(i % 2))) && + data->use_vref) { + dev_dbg(dev, "ch[%u]: was configured to use internal band gap", i); + dev_dbg(dev, "ch[%u]: reference voltage set to VREF", i); + break; + } + break; + case MCP47FEB02_EXTERNAL_VREF_UNBUFFERED: + case MCP47FEB02_EXTERNAL_VREF_BUFFERED: + if (data->phys_channels >= 4 && (i % 2) && !data->use_vref1) { + dev_dbg(dev, "ch[%u]: was configured to use VREF1", i); + dev_dbg(dev, + "ch[%u]: reference voltage set to internal band gap", i); + break; + } + if ((data->phys_channels < 4 || (data->phys_channels >= 4 && !(i % 2))) && + !data->use_vref) { + dev_dbg(dev, "ch[%u]: was configured to use VREF", i); + dev_dbg(dev, + "ch[%u]: reference voltage set to internal band gap", i); + break; + } + break; + } + + pd_tmp = (pd_ch >> (2 * i)) & MCP47FEB02_DAC_CTRL_MASK; + data->chdata[i].powerdown_mode = pd_tmp ? (pd_tmp - 1) : pd_tmp; + data->chdata[i].powerdown = !!(data->chdata[i].powerdown_mode); + } + + return 0; +} + +static int mcp47feb02_init_ch_scales(struct mcp47feb02_data *data, int vdd_mV, + int vref_mV, int vref1_mV) +{ + unsigned int i; + + for_each_set_bit(i, &data->active_channels_mask, data->phys_channels) { + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = mcp47feb02_init_scales_avail(data, vdd_mV, vref_mV, vref1_mV); + if (ret) + return dev_err_probe(dev, ret, "failed to init scales for ch %u\n", i); + } + + return 0; +} + +static int mcp47feb02_probe(struct i2c_client *client) +{ + const struct mcp47feb02_features *chip_features; + struct device *dev = &client->dev; + struct mcp47feb02_data *data; + struct iio_dev *indio_dev; + int vref1_mV = 0; + int vref_mV = 0; + int vdd_mV; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + chip_features = i2c_get_match_data(client); + if (!chip_features) + return -EINVAL; + + data->chip_features = chip_features; + + if (chip_features->have_eeprom) { + data->regmap = devm_regmap_init_i2c(client, &mcp47feb02_regmap_config); + indio_dev->info = &mcp47feb02_info; + } else { + data->regmap = devm_regmap_init_i2c(client, &mcp47fvb02_regmap_config); + indio_dev->info = &mcp47fvb02_info; + } + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), "Error initializing i2c regmap\n"); + + indio_dev->name = chip_features->name; + + ret = mcp47feb02_parse_fw(indio_dev, chip_features); + if (ret) + return dev_err_probe(dev, ret, "Error parsing firmware data\n"); + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + ret = devm_regulator_get_enable_read_voltage(dev, "vdd"); + if (ret < 0) + return ret; + + vdd_mV = ret / MILLI; + + ret = devm_regulator_get_enable_read_voltage(dev, "vref"); + if (ret > 0) { + vref_mV = ret / MILLI; + data->use_vref = true; + } else { + dev_dbg(dev, "using internal band gap as voltage reference.\n"); + dev_dbg(dev, "Vref is unavailable.\n"); + } + + if (chip_features->have_ext_vref1) { + ret = devm_regulator_get_enable_read_voltage(dev, "vref1"); + if (ret > 0) { + vref1_mV = ret / MILLI; + data->use_vref1 = true; + } else { + dev_dbg(dev, "using internal band gap as voltage reference 1.\n"); + dev_dbg(dev, "Vref1 is unavailable.\n"); + } + } + + ret = mcp47feb02_init_ctrl_regs(data); + if (ret) + return dev_err_probe(dev, ret, "Error initialising vref register\n"); + + ret = mcp47feb02_init_ch_scales(data, vdd_mV, vref_mV, vref1_mV); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct i2c_device_id mcp47feb02_id[] = { + { "mcp47feb01", (kernel_ulong_t)&mcp47feb01_chip_features }, + { "mcp47feb02", (kernel_ulong_t)&mcp47feb02_chip_features }, + { "mcp47feb04", (kernel_ulong_t)&mcp47feb04_chip_features }, + { "mcp47feb08", (kernel_ulong_t)&mcp47feb08_chip_features }, + { "mcp47feb11", (kernel_ulong_t)&mcp47feb11_chip_features }, + { "mcp47feb12", (kernel_ulong_t)&mcp47feb12_chip_features }, + { "mcp47feb14", (kernel_ulong_t)&mcp47feb14_chip_features }, + { "mcp47feb18", (kernel_ulong_t)&mcp47feb18_chip_features }, + { "mcp47feb21", (kernel_ulong_t)&mcp47feb21_chip_features }, + { "mcp47feb22", (kernel_ulong_t)&mcp47feb22_chip_features }, + { "mcp47feb24", (kernel_ulong_t)&mcp47feb24_chip_features }, + { "mcp47feb28", (kernel_ulong_t)&mcp47feb28_chip_features }, + { "mcp47fvb01", (kernel_ulong_t)&mcp47fvb01_chip_features }, + { "mcp47fvb02", (kernel_ulong_t)&mcp47fvb02_chip_features }, + { "mcp47fvb04", (kernel_ulong_t)&mcp47fvb04_chip_features }, + { "mcp47fvb08", (kernel_ulong_t)&mcp47fvb08_chip_features }, + { "mcp47fvb11", (kernel_ulong_t)&mcp47fvb11_chip_features }, + { "mcp47fvb12", (kernel_ulong_t)&mcp47fvb12_chip_features }, + { "mcp47fvb14", (kernel_ulong_t)&mcp47fvb14_chip_features }, + { "mcp47fvb18", (kernel_ulong_t)&mcp47fvb18_chip_features }, + { "mcp47fvb21", (kernel_ulong_t)&mcp47fvb21_chip_features }, + { "mcp47fvb22", (kernel_ulong_t)&mcp47fvb22_chip_features }, + { "mcp47fvb24", (kernel_ulong_t)&mcp47fvb24_chip_features }, + { "mcp47fvb28", (kernel_ulong_t)&mcp47fvb28_chip_features }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mcp47feb02_id); + +static const struct of_device_id mcp47feb02_of_match[] = { + { .compatible = "microchip,mcp47feb01", .data = &mcp47feb01_chip_features }, + { .compatible = "microchip,mcp47feb02", .data = &mcp47feb02_chip_features }, + { .compatible = "microchip,mcp47feb04", .data = &mcp47feb04_chip_features }, + { .compatible = "microchip,mcp47feb08", .data = &mcp47feb08_chip_features }, + { .compatible = "microchip,mcp47feb11", .data = &mcp47feb11_chip_features }, + { .compatible = "microchip,mcp47feb12", .data = &mcp47feb12_chip_features }, + { .compatible = "microchip,mcp47feb14", .data = &mcp47feb14_chip_features }, + { .compatible = "microchip,mcp47feb18", .data = &mcp47feb18_chip_features }, + { .compatible = "microchip,mcp47feb21", .data = &mcp47feb21_chip_features }, + { .compatible = "microchip,mcp47feb22", .data = &mcp47feb22_chip_features }, + { .compatible = "microchip,mcp47feb24", .data = &mcp47feb24_chip_features }, + { .compatible = "microchip,mcp47feb28", .data = &mcp47feb28_chip_features }, + { .compatible = "microchip,mcp47fvb01", .data = &mcp47fvb01_chip_features }, + { .compatible = "microchip,mcp47fvb02", .data = &mcp47fvb02_chip_features }, + { .compatible = "microchip,mcp47fvb04", .data = &mcp47fvb04_chip_features }, + { .compatible = "microchip,mcp47fvb08", .data = &mcp47fvb08_chip_features }, + { .compatible = "microchip,mcp47fvb11", .data = &mcp47fvb11_chip_features }, + { .compatible = "microchip,mcp47fvb12", .data = &mcp47fvb12_chip_features }, + { .compatible = "microchip,mcp47fvb14", .data = &mcp47fvb14_chip_features }, + { .compatible = "microchip,mcp47fvb18", .data = &mcp47fvb18_chip_features }, + { .compatible = "microchip,mcp47fvb21", .data = &mcp47fvb21_chip_features }, + { .compatible = "microchip,mcp47fvb22", .data = &mcp47fvb22_chip_features }, + { .compatible = "microchip,mcp47fvb24", .data = &mcp47fvb24_chip_features }, + { .compatible = "microchip,mcp47fvb28", .data = &mcp47fvb28_chip_features }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp47feb02_of_match); + +static struct i2c_driver mcp47feb02_driver = { + .driver = { + .name = "mcp47feb02", + .of_match_table = mcp47feb02_of_match, + .pm = pm_sleep_ptr(&mcp47feb02_pm_ops), + }, + .probe = mcp47feb02_probe, + .id_table = mcp47feb02_id, +}; +module_i2c_driver(mcp47feb02_driver); + +MODULE_AUTHOR("Ariana Lazar "); +MODULE_DESCRIPTION("IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface"); +MODULE_LICENSE("GPL"); From 9e6c7656b9977e902fe7a99d61da00e3f6bc41a4 Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Wed, 17 Dec 2025 13:13:24 +0100 Subject: [PATCH 099/297] dt-bindings: iio: adc: Add adi,ad4062 Add dt-bindings for AD4062 family, devices AD4060/AD4062, low-power with monitor capabilities SAR ADCs. Each variant of the family differs in resolution. The device contains two outputs (gp0, gp1). The outputs can be configured for range of options, such as threshold and data ready. The device uses a 2-wire I3C interface. Signed-off-by: Jorge Marques Reviewed-by: Rob Herring (Arm) Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/adi,ad4062.yaml | 120 ++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 126 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad4062.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4062.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4062.yaml new file mode 100644 index 000000000000..eeb148081663 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4062.yaml @@ -0,0 +1,120 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright 2025 Analog Devices Inc. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/adi,ad4062.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AD4062 ADC family device driver + +maintainers: + - Jorge Marques + +description: | + Analog Devices AD4062 Single Channel Precision SAR ADC family + + https://www.analog.com/media/en/technical-documentation/data-sheets/ad4060.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/ad4062.pdf + +properties: + compatible: + enum: + - adi,ad4060 + - adi,ad4062 + + reg: + maxItems: 1 + + interrupts: + description: + Two pins are available that can be configured as either a general purpose + digital output, device enable signal (used to synchronise other parts of + the signal chain with ADC sampling), device ready (GP1 only) or various + interrupt signals. If intended for use as a GPIO or device enable, will not + present here. + minItems: 1 + items: + - description: + GP0 pin, cannot be configured as DEV_RDY. + - description: + GP1 pin, can be configured to any setting. + + interrupt-names: + minItems: 1 + items: + - const: gp0 + - const: gp1 + + gpio-controller: + description: + Marks the device node as a GPIO controller. GPs not listed as interrupts + are exposed as a GPO. + + '#gpio-cells': + const: 2 + description: + The first cell is the GPIO number and the second cell specifies + GPIO flags, as defined in . + + vdd-supply: + description: Analog power supply. + + vio-supply: + description: Digital interface logic power supply. + + ref-supply: + description: + Reference voltage to set the ADC full-scale range. If not present, + vdd-supply is used as the reference voltage. + +required: + - compatible + - reg + - vdd-supply + - vio-supply + +allOf: + - $ref: /schemas/i3c/i3c.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + #include + + i3c { + #address-cells = <3>; + #size-cells = <0>; + + adc@0,2ee007c0000 { + reg = <0x0 0x2ee 0x7c0000>; + vdd-supply = <&vdd>; + vio-supply = <&vio>; + ref-supply = <&ref>; + + interrupt-parent = <&gpio>; + interrupts = <0 0 IRQ_TYPE_EDGE_RISING>, + <0 1 IRQ_TYPE_EDGE_FALLING>; + interrupt-names = "gp0", "gp1"; + }; + }; + + - | + #include + #include + + i3c { + #address-cells = <3>; + #size-cells = <0>; + + adc@0,2ee007c0000 { + reg = <0x0 0x2ee 0x7c0000>; + vdd-supply = <&vdd>; + vio-supply = <&vio>; + ref-supply = <&ref>; + + gpio-controller; + #gpio-cells = <2>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 9a42d20144a4..dfd06a31c969 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1433,6 +1433,12 @@ F: Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml F: Documentation/iio/ad4030.rst F: drivers/iio/adc/ad4030.c +ANALOG DEVICES INC AD4062 DRIVER +M: Jorge Marques +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/adc/adi,ad4062.yaml + ANALOG DEVICES INC AD4080 DRIVER M: Antoniu Miclaus L: linux-iio@vger.kernel.org From 1b1ddab0249c607ab6fc4388d6c121a23c1b5409 Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Wed, 17 Dec 2025 13:13:25 +0100 Subject: [PATCH 100/297] docs: iio: New docs for ad4062 driver This adds a new page to document how to use the ad4062 ADC driver. Signed-off-by: Jorge Marques Signed-off-by: Jonathan Cameron --- Documentation/iio/ad4062.rst | 86 ++++++++++++++++++++++++++++++++++++ Documentation/iio/index.rst | 1 + MAINTAINERS | 1 + 3 files changed, 88 insertions(+) create mode 100644 Documentation/iio/ad4062.rst diff --git a/Documentation/iio/ad4062.rst b/Documentation/iio/ad4062.rst new file mode 100644 index 000000000000..30200adf90c3 --- /dev/null +++ b/Documentation/iio/ad4062.rst @@ -0,0 +1,86 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +============= +AD4062 driver +============= + +ADC driver for Analog Devices Inc. AD4060/AD4062 devices. The module name is +``ad4062``. + +Supported devices +================= + +The following chips are supported by this driver: + +* `AD4060 `_ +* `AD4062 `_ + +Wiring modes +============ + +The ADC is interfaced through an I3C bus, and contains two programmable GPIOs. + +The ADC convert-start happens on the SDA rising edge of the I3C stop (P) bit +at the end of the read command. + +The two programmable GPIOS are optional and have a role assigned if present in +the devicetree ``interrupt-names`` property: + +- GP1: Is assigned the role of Data Ready signal. + +Device attributes +================= + +The ADC contains only one channel with following attributes: + +.. list-table:: Channel attributes + :header-rows: 1 + + * - Attribute + - Description + * - ``in_voltage_calibscale`` + - Sets the gain scaling factor that the hardware applies to the sample, + to compensate for system gain error. + * - ``in_voltage_oversampling_ratio`` + - Sets device's burst averaging mode to over sample using the + internal sample rate. Value 1 disable the burst averaging mode. + * - ``in_voltage_oversampling_ratio_available`` + - List of available oversampling values. + * - ``in_voltage_raw`` + - Returns the raw ADC voltage value. + * - ``in_voltage_scale`` + - Returns the channel scale in reference to the reference voltage + ``ref-supply`` or ``vdd-supply`` if the former not present. + +Also contain the following device attributes: + +.. list-table:: Device attributes + :header-rows: 1 + + * - Attribute + - Description + * - ``sampling_frequency`` + - Sets the duration of a single scan, used in the burst averaging mode. + The duration is described by ``(n_avg - 1) / fosc + tconv``, where + ``n_avg`` is the oversampling ratio, ``fosc`` is the internal sample + rate and ``tconv`` is the ADC conversion time. + * - ``sampling_frequency_available`` + - Lists the available sampling frequencies, computed on the current + oversampling ratio. If the ratio is 1, the frequency is ``1/tconv``. + +Interrupts +========== + +The interrupts are mapped through the ``interrupt-names`` and ``interrupts`` +properties. + +The ``interrupt-names`` ``gp1`` entry sets the role of Data Ready signal. +If it is not present, the driver fallback to enabling the same role as an +I3C IBI. + +Low-power mode +============== + +The device enters low-power mode on idle to save power. Enabling an event puts +the device out of the low-power since the ADC autonomously samples to assert +the event condition. diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst index 315ae37d6fd4..ba3e609c6a13 100644 --- a/Documentation/iio/index.rst +++ b/Documentation/iio/index.rst @@ -22,6 +22,7 @@ Industrial I/O Kernel Drivers ad3552r ad4000 ad4030 + ad4062 ad4695 ad7191 ad7380 diff --git a/MAINTAINERS b/MAINTAINERS index dfd06a31c969..2b39563b21bc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1438,6 +1438,7 @@ M: Jorge Marques S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/adc/adi,ad4062.yaml +F: Documentation/iio/ad4062.rst ANALOG DEVICES INC AD4080 DRIVER M: Antoniu Miclaus From d5284402d28f30dbbe74b9823a324a998a8306d8 Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Wed, 17 Dec 2025 13:13:26 +0100 Subject: [PATCH 101/297] iio: adc: Add support for ad4062 The AD4060/AD4062 are versatile, 16-bit/12-bit, successive approximation register (SAR) analog-to-digital converter (ADC) with low-power and threshold monitoring modes. Signed-off-by: Jorge Marques Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 11 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ad4062.c | 819 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 832 insertions(+) create mode 100644 drivers/iio/adc/ad4062.c diff --git a/MAINTAINERS b/MAINTAINERS index 2b39563b21bc..2e8825b8ccef 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1439,6 +1439,7 @@ S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/adc/adi,ad4062.yaml F: Documentation/iio/ad4062.rst +F: drivers/iio/adc/ad4062.c ANALOG DEVICES INC AD4080 DRIVER M: Antoniu Miclaus diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 576480e3ac4a..fda0da422c67 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -70,6 +70,17 @@ config AD4030 To compile this driver as a module, choose M here: the module will be called ad4030. +config AD4062 + tristate "Analog Devices AD4062 Driver" + depends on I3C + select REGMAP_I3C + help + Say yes here to build support for Analog Devices AD4062 I3C analog + to digital converters (ADC). + + To compile this driver as a module, choose M here: the module will be + called ad4062. + config AD4080 tristate "Analog Devices AD4080 high speed ADC" depends on SPI diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index b1b1d5a2273f..0a199630c081 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_AB8500_GPADC) += ab8500-gpadc.o obj-$(CONFIG_AD_SIGMA_DELTA) += ad_sigma_delta.o obj-$(CONFIG_AD4000) += ad4000.o obj-$(CONFIG_AD4030) += ad4030.o +obj-$(CONFIG_AD4062) += ad4062.o obj-$(CONFIG_AD4080) += ad4080.o obj-$(CONFIG_AD4130) += ad4130.o obj-$(CONFIG_AD4170_4) += ad4170-4.o diff --git a/drivers/iio/adc/ad4062.c b/drivers/iio/adc/ad4062.c new file mode 100644 index 000000000000..1a7829c507e5 --- /dev/null +++ b/drivers/iio/adc/ad4062.c @@ -0,0 +1,819 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices AD4062 I3C ADC driver + * + * Copyright 2025 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AD4062_REG_INTERFACE_CONFIG_A 0x00 +#define AD4062_REG_DEVICE_CONFIG 0x02 +#define AD4062_REG_DEVICE_CONFIG_POWER_MODE_MSK GENMASK(1, 0) +#define AD4062_REG_DEVICE_CONFIG_LOW_POWER_MODE 3 +#define AD4062_REG_PROD_ID_1 0x05 +#define AD4062_REG_DEVICE_GRADE 0x06 +#define AD4062_REG_SCRATCH_PAD 0x0A +#define AD4062_REG_VENDOR_H 0x0D +#define AD4062_REG_STREAM_MODE 0x0E +#define AD4062_REG_INTERFACE_STATUS 0x11 +#define AD4062_REG_MODE_SET 0x20 +#define AD4062_REG_MODE_SET_ENTER_ADC BIT(0) +#define AD4062_REG_ADC_MODES 0x21 +#define AD4062_REG_ADC_MODES_MODE_MSK GENMASK(1, 0) +#define AD4062_REG_ADC_CONFIG 0x22 +#define AD4062_REG_ADC_CONFIG_REF_EN_MSK BIT(5) +#define AD4062_REG_ADC_CONFIG_SCALE_EN_MSK BIT(4) +#define AD4062_REG_AVG_CONFIG 0x23 +#define AD4062_REG_GP_CONF 0x24 +#define AD4062_REG_GP_CONF_MODE_MSK_1 GENMASK(6, 4) +#define AD4062_REG_INTR_CONF 0x25 +#define AD4062_REG_INTR_CONF_EN_MSK_1 GENMASK(5, 4) +#define AD4062_REG_TIMER_CONFIG 0x27 +#define AD4062_REG_TIMER_CONFIG_FS_MASK GENMASK(7, 4) +#define AD4062_REG_MON_VAL 0x2F +#define AD4062_REG_ADC_IBI_EN 0x31 +#define AD4062_REG_ADC_IBI_EN_CONV_TRIGGER BIT(2) +#define AD4062_REG_FUSE_CRC 0x40 +#define AD4062_REG_DEVICE_STATUS 0x41 +#define AD4062_REG_DEVICE_STATUS_DEVICE_RESET BIT(6) +#define AD4062_REG_IBI_STATUS 0x48 +#define AD4062_REG_CONV_READ_LSB 0x50 +#define AD4062_REG_CONV_TRIGGER_32BITS 0x59 +#define AD4062_REG_CONV_AUTO 0x61 +#define AD4062_MAX_REG AD4062_REG_CONV_AUTO + +#define AD4062_MON_VAL_MIDDLE_POINT 0x8000 + +#define AD4062_I3C_VENDOR 0x0177 +#define AD4062_SOFT_RESET 0x81 +#define AD4060_PROD_ID 0x7A +#define AD4062_PROD_ID 0x7C + +#define AD4062_GP_DRDY 0x2 + +#define AD4062_INTR_EN_NEITHER 0x0 + +#define AD4062_TCONV_NS 270 + +enum ad4062_operation_mode { + AD4062_SAMPLE_MODE = 0x0, + AD4062_BURST_AVERAGING_MODE = 0x1, + AD4062_MONITOR_MODE = 0x3, +}; + +struct ad4062_chip_info { + const struct iio_chan_spec channels[1]; + const char *name; + u16 prod_id; + u16 avg_max; +}; + +enum { + AD4062_SCAN_TYPE_SAMPLE, + AD4062_SCAN_TYPE_BURST_AVG, +}; + +static const unsigned int ad4062_conversion_freqs[] = { + 2000000, 1000000, 300000, 100000, /* 0 - 3 */ + 33300, 10000, 3000, 500, /* 4 - 7 */ + 333, 250, 200, 166, /* 8 - 11 */ + 140, 124, 111, /* 12 - 15 */ +}; + +struct ad4062_state { + const struct ad4062_chip_info *chip; + const struct ad4062_bus_ops *ops; + enum ad4062_operation_mode mode; + struct completion completion; + struct iio_trigger *trigger; + struct iio_dev *indio_dev; + struct i3c_device *i3cdev; + struct regmap *regmap; + int vref_uV; + unsigned int samp_freqs[ARRAY_SIZE(ad4062_conversion_freqs)]; + u16 sampling_frequency; + u8 oversamp_ratio; + u8 conv_addr; + union { + __be32 be32; + __be16 be16; + } buf __aligned(IIO_DMA_MINALIGN); +}; + +static const struct regmap_range ad4062_regmap_rd_ranges[] = { + regmap_reg_range(AD4062_REG_INTERFACE_CONFIG_A, AD4062_REG_DEVICE_GRADE), + regmap_reg_range(AD4062_REG_SCRATCH_PAD, AD4062_REG_INTERFACE_STATUS), + regmap_reg_range(AD4062_REG_MODE_SET, AD4062_REG_ADC_IBI_EN), + regmap_reg_range(AD4062_REG_FUSE_CRC, AD4062_REG_IBI_STATUS), + regmap_reg_range(AD4062_REG_CONV_READ_LSB, AD4062_REG_CONV_AUTO), +}; + +static const struct regmap_access_table ad4062_regmap_rd_table = { + .yes_ranges = ad4062_regmap_rd_ranges, + .n_yes_ranges = ARRAY_SIZE(ad4062_regmap_rd_ranges), +}; + +static const struct regmap_range ad4062_regmap_wr_ranges[] = { + regmap_reg_range(AD4062_REG_INTERFACE_CONFIG_A, AD4062_REG_DEVICE_CONFIG), + regmap_reg_range(AD4062_REG_SCRATCH_PAD, AD4062_REG_SCRATCH_PAD), + regmap_reg_range(AD4062_REG_STREAM_MODE, AD4062_REG_INTERFACE_STATUS), + regmap_reg_range(AD4062_REG_MODE_SET, AD4062_REG_ADC_IBI_EN), + regmap_reg_range(AD4062_REG_FUSE_CRC, AD4062_REG_DEVICE_STATUS), +}; + +static const struct regmap_access_table ad4062_regmap_wr_table = { + .yes_ranges = ad4062_regmap_wr_ranges, + .n_yes_ranges = ARRAY_SIZE(ad4062_regmap_wr_ranges), +}; + +#define AD4062_CHAN { \ + .type = IIO_VOLTAGE, \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .indexed = 1, \ + .channel = 0, \ +} + +static const struct ad4062_chip_info ad4060_chip_info = { + .name = "ad4060", + .channels = { AD4062_CHAN }, + .prod_id = AD4060_PROD_ID, + .avg_max = 256, +}; + +static const struct ad4062_chip_info ad4062_chip_info = { + .name = "ad4062", + .channels = { AD4062_CHAN }, + .prod_id = AD4062_PROD_ID, + .avg_max = 4096, +}; + +static int ad4062_set_oversampling_ratio(struct ad4062_state *st, int val, int val2) +{ + const u32 _max = st->chip->avg_max; + const u32 _min = 1; + int ret; + + if (!in_range(val, _min, _max) || val2 != 0) + return -EINVAL; + + /* 1 disables oversampling */ + val = ilog2(val); + if (val == 0) { + st->mode = AD4062_SAMPLE_MODE; + } else { + st->mode = AD4062_BURST_AVERAGING_MODE; + ret = regmap_write(st->regmap, AD4062_REG_AVG_CONFIG, val - 1); + if (ret) + return ret; + } + st->oversamp_ratio = val; + + return 0; +} + +static int ad4062_get_oversampling_ratio(struct ad4062_state *st, int *val) +{ + int ret, buf; + + if (st->mode == AD4062_SAMPLE_MODE) { + *val = 1; + return 0; + } + + ret = regmap_read(st->regmap, AD4062_REG_AVG_CONFIG, &buf); + if (ret) + return ret; + + *val = BIT(buf + 1); + return 0; +} + +static int ad4062_calc_sampling_frequency(unsigned int fosc, unsigned int oversamp_ratio) +{ + /* From datasheet p.31: (n_avg - 1)/fosc + tconv */ + u32 n_avg = BIT(oversamp_ratio) - 1; + u32 period_ns = NSEC_PER_SEC / fosc; + + /* Result is less than 1 Hz */ + if (n_avg >= fosc) + return 1; + + return NSEC_PER_SEC / (n_avg * period_ns + AD4062_TCONV_NS); +} + +static int ad4062_populate_sampling_frequency(struct ad4062_state *st) +{ + for (u8 i = 0; i < ARRAY_SIZE(ad4062_conversion_freqs); i++) + st->samp_freqs[i] = + ad4062_calc_sampling_frequency(ad4062_conversion_freqs[i], + st->oversamp_ratio); + return 0; +} + +static int ad4062_get_sampling_frequency(struct ad4062_state *st, int *val) +{ + int freq = ad4062_conversion_freqs[st->sampling_frequency]; + + *val = ad4062_calc_sampling_frequency(freq, st->oversamp_ratio); + return IIO_VAL_INT; +} + +static int ad4062_set_sampling_frequency(struct ad4062_state *st, int val, int val2) +{ + int ret; + + if (val2 != 0) + return -EINVAL; + + ret = ad4062_populate_sampling_frequency(st); + if (ret) + return ret; + + st->sampling_frequency = + find_closest_descending(val, st->samp_freqs, + ARRAY_SIZE(ad4062_conversion_freqs)); + return 0; +} + +static int ad4062_check_ids(struct ad4062_state *st) +{ + struct device *dev = &st->i3cdev->dev; + int ret; + u16 val; + + ret = regmap_bulk_read(st->regmap, AD4062_REG_PROD_ID_1, + &st->buf.be16, sizeof(st->buf.be16)); + if (ret) + return ret; + + val = be16_to_cpu(st->buf.be16); + if (val != st->chip->prod_id) + dev_warn(dev, "Production ID x%x does not match known values", val); + + ret = regmap_bulk_read(st->regmap, AD4062_REG_VENDOR_H, + &st->buf.be16, sizeof(st->buf.be16)); + if (ret) + return ret; + + val = be16_to_cpu(st->buf.be16); + if (val != AD4062_I3C_VENDOR) { + dev_err(dev, "Vendor ID x%x does not match expected value\n", val); + return -ENODEV; + } + + return 0; +} + +static int ad4062_conversion_frequency_set(struct ad4062_state *st, u8 val) +{ + return regmap_write(st->regmap, AD4062_REG_TIMER_CONFIG, + FIELD_PREP(AD4062_REG_TIMER_CONFIG_FS_MASK, val)); +} + +static int ad4062_set_operation_mode(struct ad4062_state *st, + enum ad4062_operation_mode mode) +{ + int ret; + + ret = ad4062_conversion_frequency_set(st, st->sampling_frequency); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, AD4062_REG_ADC_MODES, + AD4062_REG_ADC_MODES_MODE_MSK, mode); + if (ret) + return ret; + + return regmap_write(st->regmap, AD4062_REG_MODE_SET, + AD4062_REG_MODE_SET_ENTER_ADC); +} + +static int ad4062_soft_reset(struct ad4062_state *st) +{ + u8 val = AD4062_SOFT_RESET; + int ret; + + ret = regmap_write(st->regmap, AD4062_REG_INTERFACE_CONFIG_A, val); + if (ret) + return ret; + + /* Wait AD4062 treset time, datasheet p8 */ + ndelay(60); + + return 0; +} + +static int ad4062_setup(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + const bool *ref_sel) +{ + struct ad4062_state *st = iio_priv(indio_dev); + int ret; + + ret = regmap_update_bits(st->regmap, AD4062_REG_GP_CONF, + AD4062_REG_GP_CONF_MODE_MSK_1, + FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_1, + AD4062_GP_DRDY)); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, AD4062_REG_ADC_CONFIG, + AD4062_REG_ADC_CONFIG_REF_EN_MSK, + FIELD_PREP(AD4062_REG_ADC_CONFIG_REF_EN_MSK, + *ref_sel)); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4062_REG_DEVICE_STATUS, + AD4062_REG_DEVICE_STATUS_DEVICE_RESET); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, AD4062_REG_INTR_CONF, + AD4062_REG_INTR_CONF_EN_MSK_1, + FIELD_PREP(AD4062_REG_INTR_CONF_EN_MSK_1, + AD4062_INTR_EN_NEITHER)); + if (ret) + return ret; + + st->buf.be16 = cpu_to_be16(AD4062_MON_VAL_MIDDLE_POINT); + return regmap_bulk_write(st->regmap, AD4062_REG_MON_VAL, + &st->buf.be16, sizeof(st->buf.be16)); +} + +static irqreturn_t ad4062_irq_handler_drdy(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ad4062_state *st = iio_priv(indio_dev); + + complete(&st->completion); + + return IRQ_HANDLED; +} + +static void ad4062_ibi_handler(struct i3c_device *i3cdev, + const struct i3c_ibi_payload *payload) +{ + struct ad4062_state *st = i3cdev_get_drvdata(i3cdev); + + complete(&st->completion); +} + +static void ad4062_disable_ibi(void *data) +{ + struct i3c_device *i3cdev = data; + + i3c_device_disable_ibi(i3cdev); +} + +static void ad4062_free_ibi(void *data) +{ + struct i3c_device *i3cdev = data; + + i3c_device_free_ibi(i3cdev); +} + +static int ad4062_request_ibi(struct i3c_device *i3cdev) +{ + const struct i3c_ibi_setup ibireq = { + .max_payload_len = 1, + .num_slots = 1, + .handler = ad4062_ibi_handler, + }; + int ret; + + ret = i3c_device_request_ibi(i3cdev, &ibireq); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&i3cdev->dev, ad4062_free_ibi, i3cdev); + if (ret) + return ret; + + ret = i3c_device_enable_ibi(i3cdev); + if (ret) + return ret; + + return devm_add_action_or_reset(&i3cdev->dev, ad4062_disable_ibi, i3cdev); +} + +static int ad4062_request_irq(struct iio_dev *indio_dev) +{ + struct ad4062_state *st = iio_priv(indio_dev); + struct device *dev = &st->i3cdev->dev; + int ret; + + ret = fwnode_irq_get_byname(dev_fwnode(&st->i3cdev->dev), "gp1"); + if (ret == -EPROBE_DEFER) + return ret; + + if (ret < 0) + return regmap_update_bits(st->regmap, AD4062_REG_ADC_IBI_EN, + AD4062_REG_ADC_IBI_EN_CONV_TRIGGER, + AD4062_REG_ADC_IBI_EN_CONV_TRIGGER); + + return devm_request_threaded_irq(dev, ret, + ad4062_irq_handler_drdy, + NULL, IRQF_ONESHOT, indio_dev->name, + indio_dev); +} + +static const int ad4062_oversampling_avail[] = { + 1, 2, 4, 8, 16, 32, 64, 128, /* 0 - 7 */ + 256, 512, 1024, 2048, 4096, /* 8 - 12 */ +}; + +static int ad4062_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, const int **vals, + int *type, int *len, long mask) +{ + struct ad4062_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = ad4062_oversampling_avail; + *len = ARRAY_SIZE(ad4062_oversampling_avail); + *len -= st->chip->avg_max == 256 ? 4 : 0; + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = ad4062_populate_sampling_frequency(st); + if (ret) + return ret; + *vals = st->samp_freqs; + *len = st->oversamp_ratio ? ARRAY_SIZE(ad4062_conversion_freqs) : 1; + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int ad4062_get_chan_calibscale(struct ad4062_state *st, int *val, int *val2) +{ + int ret; + + ret = regmap_bulk_read(st->regmap, AD4062_REG_MON_VAL, + &st->buf.be16, sizeof(st->buf.be16)); + if (ret) + return ret; + + /* From datasheet: code out = code in × mon_val/0x8000 */ + *val = be16_to_cpu(st->buf.be16) * 2; + *val2 = 16; + + return IIO_VAL_FRACTIONAL_LOG2; +} + +static int ad4062_set_chan_calibscale(struct ad4062_state *st, int gain_int, + int gain_frac) +{ + /* Divide numerator and denumerator by known great common divider */ + const u32 mon_val = AD4062_MON_VAL_MIDDLE_POINT / 64; + const u32 micro = MICRO / 64; + const u32 gain_fp = gain_int * MICRO + gain_frac; + const u32 reg_val = DIV_ROUND_CLOSEST(gain_fp * mon_val, micro); + int ret; + + /* Checks if the gain is in range and the value fits the field */ + if (gain_int < 0 || gain_int > 1 || reg_val > BIT(16) - 1) + return -EINVAL; + + st->buf.be16 = cpu_to_be16(reg_val); + ret = regmap_bulk_write(st->regmap, AD4062_REG_MON_VAL, + &st->buf.be16, sizeof(st->buf.be16)); + if (ret) + return ret; + + /* Enable scale if gain is not equal to one */ + return regmap_update_bits(st->regmap, AD4062_REG_ADC_CONFIG, + AD4062_REG_ADC_CONFIG_SCALE_EN_MSK, + FIELD_PREP(AD4062_REG_ADC_CONFIG_SCALE_EN_MSK, + !(gain_int == 1 && gain_frac == 0))); +} + +static int ad4062_read_chan_raw(struct ad4062_state *st, int *val) +{ + struct i3c_device *i3cdev = st->i3cdev; + struct i3c_priv_xfer xfer_trigger = { + .data.out = &st->conv_addr, + .len = sizeof(st->conv_addr), + .rnw = false, + }; + struct i3c_priv_xfer xfer_sample = { + .data.in = &st->buf.be32, + .len = sizeof(st->buf.be32), + .rnw = true, + }; + int ret; + + PM_RUNTIME_ACQUIRE(&st->i3cdev->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = ad4062_set_operation_mode(st, st->mode); + if (ret) + return ret; + + reinit_completion(&st->completion); + /* Change address pointer to trigger conversion */ + st->conv_addr = AD4062_REG_CONV_TRIGGER_32BITS; + ret = i3c_device_do_priv_xfers(i3cdev, &xfer_trigger, 1); + if (ret) + return ret; + /* + * Single sample read should be used only for oversampling and + * sampling frequency pairs that take less than 1 sec. + */ + ret = wait_for_completion_timeout(&st->completion, + msecs_to_jiffies(1000)); + if (!ret) + return -ETIMEDOUT; + + ret = i3c_device_do_priv_xfers(i3cdev, &xfer_sample, 1); + if (ret) + return ret; + *val = be32_to_cpu(st->buf.be32); + return 0; +} + +static int ad4062_read_raw_dispatch(struct ad4062_state *st, + int *val, int *val2, long info) +{ + switch (info) { + case IIO_CHAN_INFO_RAW: + return ad4062_read_chan_raw(st, val); + + case IIO_CHAN_INFO_CALIBSCALE: + return ad4062_get_chan_calibscale(st, val, val2); + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return ad4062_get_oversampling_ratio(st, val); + + default: + return -EINVAL; + } +} + +static int ad4062_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad4062_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + return ad4062_get_sampling_frequency(st, val); + } + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad4062_read_raw_dispatch(st, val, val2, info); + iio_device_release_direct(indio_dev); + return ret ?: IIO_VAL_INT; +} + +static int ad4062_write_raw_dispatch(struct ad4062_state *st, int val, int val2, + long info) +{ + switch (info) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return ad4062_set_oversampling_ratio(st, val, val2); + + case IIO_CHAN_INFO_CALIBSCALE: + return ad4062_set_chan_calibscale(st, val, val2); + + default: + return -EINVAL; + } +}; + +static int ad4062_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long info) +{ + struct ad4062_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + return ad4062_set_sampling_frequency(st, val, val2); + } + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad4062_write_raw_dispatch(st, val, val2, info); + + iio_device_release_direct(indio_dev); + return ret; +} + +static int ad4062_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ad4062_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + else + return regmap_write(st->regmap, reg, writeval); +} + +static const struct iio_info ad4062_info = { + .read_raw = ad4062_read_raw, + .write_raw = ad4062_write_raw, + .read_avail = ad4062_read_avail, + .debugfs_reg_access = ad4062_debugfs_reg_access, +}; + +static const struct regmap_config ad4062_regmap_config = { + .name = "ad4062", + .reg_bits = 8, + .val_bits = 8, + .max_register = AD4062_MAX_REG, + .rd_table = &ad4062_regmap_rd_table, + .wr_table = &ad4062_regmap_wr_table, + .can_sleep = true, +}; + +static int ad4062_regulators_get(struct ad4062_state *st, bool *ref_sel) +{ + struct device *dev = &st->i3cdev->dev; + int ret; + + ret = devm_regulator_get_enable(dev, "vio"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable vio voltage\n"); + + st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "ref"); + *ref_sel = st->vref_uV == -ENODEV; + if (st->vref_uV < 0 && !*ref_sel) + return dev_err_probe(dev, st->vref_uV, + "Failed to enable and read ref voltage\n"); + + if (*ref_sel) { + st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "vdd"); + if (st->vref_uV < 0) + return dev_err_probe(dev, st->vref_uV, + "Failed to enable and read vdd voltage\n"); + } else { + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable vdd regulator\n"); + } + + return 0; +} + +static const struct i3c_device_id ad4062_id_table[] = { + I3C_DEVICE(AD4062_I3C_VENDOR, AD4060_PROD_ID, &ad4060_chip_info), + I3C_DEVICE(AD4062_I3C_VENDOR, AD4062_PROD_ID, &ad4062_chip_info), + { } +}; +MODULE_DEVICE_TABLE(i3c, ad4062_id_table); + +static int ad4062_probe(struct i3c_device *i3cdev) +{ + const struct i3c_device_id *id = i3c_device_match_id(i3cdev, ad4062_id_table); + const struct ad4062_chip_info *chip = id->data; + struct device *dev = &i3cdev->dev; + struct iio_dev *indio_dev; + struct ad4062_state *st; + bool ref_sel; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->i3cdev = i3cdev; + i3cdev_set_drvdata(i3cdev, st); + init_completion(&st->completion); + + ret = ad4062_regulators_get(st, &ref_sel); + if (ret) + return ret; + + st->regmap = devm_regmap_init_i3c(i3cdev, &ad4062_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "Failed to initialize regmap\n"); + + st->mode = AD4062_SAMPLE_MODE; + st->chip = chip; + st->sampling_frequency = 0; + st->oversamp_ratio = 0; + st->indio_dev = indio_dev; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = 1; + indio_dev->info = &ad4062_info; + indio_dev->name = chip->name; + indio_dev->channels = chip->channels; + + ret = ad4062_soft_reset(st); + if (ret) + return dev_err_probe(dev, ret, "AD4062 failed to soft reset\n"); + + ret = ad4062_check_ids(st); + if (ret) + return ret; + + ret = ad4062_setup(indio_dev, indio_dev->channels, &ref_sel); + if (ret) + return ret; + + ret = ad4062_request_irq(indio_dev); + if (ret) + return ret; + + pm_runtime_set_active(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable pm_runtime\n"); + + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + + ret = ad4062_request_ibi(i3cdev); + if (ret) + return dev_err_probe(dev, ret, "Failed to request i3c ibi\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static int ad4062_runtime_suspend(struct device *dev) +{ + struct ad4062_state *st = dev_get_drvdata(dev); + + return regmap_write(st->regmap, AD4062_REG_DEVICE_CONFIG, + FIELD_PREP(AD4062_REG_DEVICE_CONFIG_POWER_MODE_MSK, + AD4062_REG_DEVICE_CONFIG_LOW_POWER_MODE)); +} + +static int ad4062_runtime_resume(struct device *dev) +{ + struct ad4062_state *st = dev_get_drvdata(dev); + int ret; + + ret = regmap_clear_bits(st->regmap, AD4062_REG_DEVICE_CONFIG, + AD4062_REG_DEVICE_CONFIG_POWER_MODE_MSK); + if (ret) + return ret; + + /* Wait device functional blocks to power up */ + fsleep(3 * USEC_PER_MSEC); + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(ad4062_pm_ops, + ad4062_runtime_suspend, ad4062_runtime_resume, NULL); + +static struct i3c_driver ad4062_driver = { + .driver = { + .name = "ad4062", + .pm = pm_ptr(&ad4062_pm_ops), + }, + .probe = ad4062_probe, + .id_table = ad4062_id_table, +}; +module_i3c_driver(ad4062_driver); + +MODULE_AUTHOR("Jorge Marques "); +MODULE_DESCRIPTION("Analog Devices AD4062"); +MODULE_LICENSE("GPL"); From c31721dc0bb5aaf1b93f26193fece15b857f5783 Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Wed, 17 Dec 2025 13:13:27 +0100 Subject: [PATCH 102/297] docs: iio: ad4062: Add IIO Trigger support Explains the IIO Trigger support and timings involved. Signed-off-by: Jorge Marques Signed-off-by: Jonathan Cameron --- Documentation/iio/ad4062.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Documentation/iio/ad4062.rst b/Documentation/iio/ad4062.rst index 30200adf90c3..12ac16dfc302 100644 --- a/Documentation/iio/ad4062.rst +++ b/Documentation/iio/ad4062.rst @@ -84,3 +84,16 @@ Low-power mode The device enters low-power mode on idle to save power. Enabling an event puts the device out of the low-power since the ADC autonomously samples to assert the event condition. + +IIO trigger support +=================== + +An IIO trigger ``ad4062-devX`` is registered by the driver to be used by the +same device, to capture samples to a software buffer. It is required to attach +the trigger to the device by setting the ``current_trigger`` before enabling +and reading the buffer. + +The acquisition is sequential and bounded by the protocol timings, software +latency and internal timings, the sample rate is not configurable. The burst +averaging mode does impact the effective sample rate, since it increases the +internal timing to output a single sample. From 23cc92280302d4e4f5f4253b6ff1dbeeb82a9464 Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Wed, 17 Dec 2025 13:13:28 +0100 Subject: [PATCH 103/297] iio: adc: ad4062: Add IIO Trigger support Adds support for IIO Trigger. Optionally, gp1 is assigned as Data Ready signal, if not present, fallback to an I3C IBI with the same role. The software trigger is allocated by the device, but must be attached by the user before enabling the buffer. The purpose is to not impede removing the driver due to the increased reference count when iio_trigger_set_immutable() or iio_trigger_get() is used. Signed-off-by: Jorge Marques Signed-off-by: Jonathan Cameron --- drivers/iio/adc/Kconfig | 2 + drivers/iio/adc/ad4062.c | 272 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 268 insertions(+), 6 deletions(-) diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index fda0da422c67..89a6486135f6 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -74,6 +74,8 @@ config AD4062 tristate "Analog Devices AD4062 Driver" depends on I3C select REGMAP_I3C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER help Say yes here to build support for Analog Devices AD4062 I3C analog to digital converters (ADC). diff --git a/drivers/iio/adc/ad4062.c b/drivers/iio/adc/ad4062.c index 1a7829c507e5..afc8b6969cf0 100644 --- a/drivers/iio/adc/ad4062.c +++ b/drivers/iio/adc/ad4062.c @@ -9,10 +9,15 @@ #include #include #include +#include #include #include #include +#include #include +#include +#include +#include #include #include #include @@ -59,6 +64,9 @@ #define AD4062_REG_DEVICE_STATUS_DEVICE_RESET BIT(6) #define AD4062_REG_IBI_STATUS 0x48 #define AD4062_REG_CONV_READ_LSB 0x50 +#define AD4062_REG_CONV_READ_16BITS 0x51 +#define AD4062_REG_CONV_READ_32BITS 0x53 +#define AD4062_REG_CONV_TRIGGER_16BITS 0x57 #define AD4062_REG_CONV_TRIGGER_32BITS 0x59 #define AD4062_REG_CONV_AUTO 0x61 #define AD4062_MAX_REG AD4062_REG_CONV_AUTO @@ -94,6 +102,36 @@ enum { AD4062_SCAN_TYPE_BURST_AVG, }; +static const struct iio_scan_type ad4062_scan_type_12_s[] = { + [AD4062_SCAN_TYPE_SAMPLE] = { + .sign = 's', + .realbits = 12, + .storagebits = 16, + .endianness = IIO_BE, + }, + [AD4062_SCAN_TYPE_BURST_AVG] = { + .sign = 's', + .realbits = 14, + .storagebits = 16, + .endianness = IIO_BE, + }, +}; + +static const struct iio_scan_type ad4062_scan_type_16_s[] = { + [AD4062_SCAN_TYPE_SAMPLE] = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + [AD4062_SCAN_TYPE_BURST_AVG] = { + .sign = 's', + .realbits = 20, + .storagebits = 32, + .endianness = IIO_BE, + }, +}; + static const unsigned int ad4062_conversion_freqs[] = { 2000000, 1000000, 300000, 100000, /* 0 - 3 */ 33300, 10000, 3000, 500, /* 4 - 7 */ @@ -105,6 +143,7 @@ struct ad4062_state { const struct ad4062_chip_info *chip; const struct ad4062_bus_ops *ops; enum ad4062_operation_mode mode; + struct work_struct trig_conv; struct completion completion; struct iio_trigger *trigger; struct iio_dev *indio_dev; @@ -112,8 +151,10 @@ struct ad4062_state { struct regmap *regmap; int vref_uV; unsigned int samp_freqs[ARRAY_SIZE(ad4062_conversion_freqs)]; + bool gpo_irq[2]; u16 sampling_frequency; u8 oversamp_ratio; + u8 conv_sizeof; u8 conv_addr; union { __be32 be32; @@ -147,7 +188,7 @@ static const struct regmap_access_table ad4062_regmap_wr_table = { .n_yes_ranges = ARRAY_SIZE(ad4062_regmap_wr_ranges), }; -#define AD4062_CHAN { \ +#define AD4062_CHAN(bits) { \ .type = IIO_VOLTAGE, \ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_RAW) | \ BIT(IIO_CHAN_INFO_SCALE) | \ @@ -158,18 +199,21 @@ static const struct regmap_access_table ad4062_regmap_wr_table = { .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ .indexed = 1, \ .channel = 0, \ + .has_ext_scan_type = 1, \ + .ext_scan_type = ad4062_scan_type_##bits##_s, \ + .num_ext_scan_type = ARRAY_SIZE(ad4062_scan_type_##bits##_s), \ } static const struct ad4062_chip_info ad4060_chip_info = { .name = "ad4060", - .channels = { AD4062_CHAN }, + .channels = { AD4062_CHAN(12) }, .prod_id = AD4060_PROD_ID, .avg_max = 256, }; static const struct ad4062_chip_info ad4062_chip_info = { .name = "ad4062", - .channels = { AD4062_CHAN }, + .channels = { AD4062_CHAN(16) }, .prod_id = AD4062_PROD_ID, .avg_max = 4096, }; @@ -334,8 +378,13 @@ static int ad4062_setup(struct iio_dev *indio_dev, struct iio_chan_spec const *c const bool *ref_sel) { struct ad4062_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; int ret; + scan_type = iio_get_current_scan_type(indio_dev, chan); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + ret = regmap_update_bits(st->regmap, AD4062_REG_GP_CONF, AD4062_REG_GP_CONF_MODE_MSK_1, FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_1, @@ -372,7 +421,10 @@ static irqreturn_t ad4062_irq_handler_drdy(int irq, void *private) struct iio_dev *indio_dev = private; struct ad4062_state *st = iio_priv(indio_dev); - complete(&st->completion); + if (iio_buffer_enabled(indio_dev) && iio_trigger_using_own(indio_dev)) + iio_trigger_poll(st->trigger); + else + complete(&st->completion); return IRQ_HANDLED; } @@ -382,7 +434,55 @@ static void ad4062_ibi_handler(struct i3c_device *i3cdev, { struct ad4062_state *st = i3cdev_get_drvdata(i3cdev); - complete(&st->completion); + if (iio_buffer_enabled(st->indio_dev)) + iio_trigger_poll_nested(st->trigger); + else + complete(&st->completion); +} + +static void ad4062_trigger_work(struct work_struct *work) +{ + struct ad4062_state *st = + container_of(work, struct ad4062_state, trig_conv); + int ret; + + /* + * Read current conversion, if at reg CONV_READ, stop bit triggers + * next sample and does not need writing the address. + */ + struct i3c_priv_xfer xfer_sample = { + .data.in = &st->buf.be32, + .len = st->conv_sizeof, + .rnw = true, + }; + struct i3c_priv_xfer xfer_trigger = { + .data.out = &st->conv_addr, + .len = sizeof(st->conv_addr), + .rnw = false, + }; + + ret = i3c_device_do_priv_xfers(st->i3cdev, &xfer_sample, 1); + if (ret) + return; + + iio_push_to_buffers_with_ts(st->indio_dev, &st->buf.be32, st->conv_sizeof, + iio_get_time_ns(st->indio_dev)); + if (st->gpo_irq[1]) + return; + + i3c_device_do_priv_xfers(st->i3cdev, &xfer_trigger, 1); +} + +static irqreturn_t ad4062_poll_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad4062_state *st = iio_priv(indio_dev); + + iio_trigger_notify_done(indio_dev->trig); + schedule_work(&st->trig_conv); + + return IRQ_HANDLED; } static void ad4062_disable_ibi(void *data) @@ -433,10 +533,13 @@ static int ad4062_request_irq(struct iio_dev *indio_dev) if (ret == -EPROBE_DEFER) return ret; - if (ret < 0) + if (ret < 0) { + st->gpo_irq[1] = false; return regmap_update_bits(st->regmap, AD4062_REG_ADC_IBI_EN, AD4062_REG_ADC_IBI_EN_CONV_TRIGGER, AD4062_REG_ADC_IBI_EN_CONV_TRIGGER); + } + st->gpo_irq[1] = true; return devm_request_threaded_irq(dev, ret, ad4062_irq_handler_drdy, @@ -444,6 +547,34 @@ static int ad4062_request_irq(struct iio_dev *indio_dev) indio_dev); } +static const struct iio_trigger_ops ad4062_trigger_ops = { + .validate_device = &iio_trigger_validate_own_device, +}; + +static int ad4062_request_trigger(struct iio_dev *indio_dev) +{ + struct ad4062_state *st = iio_priv(indio_dev); + struct device *dev = &st->i3cdev->dev; + int ret; + + st->trigger = devm_iio_trigger_alloc(dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!st->trigger) + return -ENOMEM; + + st->trigger->ops = &ad4062_trigger_ops; + iio_trigger_set_drvdata(st->trigger, indio_dev); + + ret = devm_iio_trigger_register(dev, st->trigger); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(st->trigger); + + return 0; +} + static const int ad4062_oversampling_avail[] = { 1, 2, 4, 8, 16, 32, 64, 128, /* 0 - 7 */ 256, 512, 1024, 2048, 4096, /* 8 - 12 */ @@ -478,6 +609,25 @@ static int ad4062_read_avail(struct iio_dev *indio_dev, } } +static int ad4062_get_chan_scale(struct iio_dev *indio_dev, int *val, int *val2) +{ + struct ad4062_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; + + /* + * In burst averaging mode the averaging filter accumulates resulting + * in a sample with increased precision. + */ + scan_type = iio_get_current_scan_type(indio_dev, st->chip->channels); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + + *val = (st->vref_uV * 2) / (MICRO / MILLI); /* signed */ + *val2 = scan_type->realbits - 1; + + return IIO_VAL_FRACTIONAL_LOG2; +} + static int ad4062_get_chan_calibscale(struct ad4062_state *st, int *val, int *val2) { int ret; @@ -593,6 +743,9 @@ static int ad4062_read_raw(struct iio_dev *indio_dev, int ret; switch (info) { + case IIO_CHAN_INFO_SCALE: + return ad4062_get_chan_scale(indio_dev, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: return ad4062_get_sampling_frequency(st, val); } @@ -641,6 +794,87 @@ static int ad4062_write_raw(struct iio_dev *indio_dev, return ret; } +/* + * The AD4062 in burst averaging mode increases realbits from 16-bits to + * 20-bits, increasing the storagebits from 16-bits to 32-bits. + */ +static inline size_t ad4062_sizeof_storagebits(struct ad4062_state *st) +{ + const struct iio_scan_type *scan_type = + iio_get_current_scan_type(st->indio_dev, st->chip->channels); + + return BITS_TO_BYTES(scan_type->storagebits); +} + +/* Read registers only with realbits (no sign extension bytes) */ +static inline size_t ad4062_get_conv_addr(struct ad4062_state *st, size_t _sizeof) +{ + if (st->gpo_irq[1]) + return _sizeof == sizeof(u32) ? AD4062_REG_CONV_READ_32BITS : + AD4062_REG_CONV_READ_16BITS; + return _sizeof == sizeof(u32) ? AD4062_REG_CONV_TRIGGER_32BITS : + AD4062_REG_CONV_TRIGGER_16BITS; +} + +static int pm_ad4062_triggered_buffer_postenable(struct ad4062_state *st) +{ + int ret; + + PM_RUNTIME_ACQUIRE(&st->i3cdev->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = ad4062_set_operation_mode(st, st->mode); + if (ret) + return ret; + + st->conv_sizeof = ad4062_sizeof_storagebits(st); + st->conv_addr = ad4062_get_conv_addr(st, st->conv_sizeof); + /* CONV_READ requires read to trigger first sample. */ + struct i3c_priv_xfer xfer_sample[2] = { + { + .data.out = &st->conv_addr, + .len = sizeof(st->conv_addr), + .rnw = false, + }, + { + .data.in = &st->buf.be32, + .len = sizeof(st->buf.be32), + .rnw = true, + } + }; + + return i3c_device_do_priv_xfers(st->i3cdev, xfer_sample, + st->gpo_irq[1] ? 2 : 1); +} + +static int ad4062_triggered_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad4062_state *st = iio_priv(indio_dev); + int ret; + + ret = pm_ad4062_triggered_buffer_postenable(st); + if (ret) + return ret; + + pm_runtime_get_noresume(&st->i3cdev->dev); + return 0; +} + +static int ad4062_triggered_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad4062_state *st = iio_priv(indio_dev); + + pm_runtime_put_autosuspend(&st->i3cdev->dev); + return 0; +} + +static const struct iio_buffer_setup_ops ad4062_triggered_buffer_setup_ops = { + .postenable = &ad4062_triggered_buffer_postenable, + .predisable = &ad4062_triggered_buffer_predisable, +}; + static int ad4062_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int reg, unsigned int writeval, unsigned int *readval) { @@ -652,10 +886,21 @@ static int ad4062_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int reg return regmap_write(st->regmap, reg, writeval); } +static int ad4062_get_current_scan_type(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad4062_state *st = iio_priv(indio_dev); + + return st->mode == AD4062_BURST_AVERAGING_MODE ? + AD4062_SCAN_TYPE_BURST_AVG : + AD4062_SCAN_TYPE_SAMPLE; +} + static const struct iio_info ad4062_info = { .read_raw = ad4062_read_raw, .write_raw = ad4062_write_raw, .read_avail = ad4062_read_avail, + .get_current_scan_type = ad4062_get_current_scan_type, .debugfs_reg_access = ad4062_debugfs_reg_access, }; @@ -762,6 +1007,17 @@ static int ad4062_probe(struct i3c_device *i3cdev) if (ret) return ret; + ret = ad4062_request_trigger(indio_dev); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(&i3cdev->dev, indio_dev, + iio_pollfunc_store_time, + ad4062_poll_handler, + &ad4062_triggered_buffer_setup_ops); + if (ret) + return ret; + pm_runtime_set_active(dev); ret = devm_pm_runtime_enable(dev); if (ret) @@ -774,6 +1030,10 @@ static int ad4062_probe(struct i3c_device *i3cdev) if (ret) return dev_err_probe(dev, ret, "Failed to request i3c ibi\n"); + ret = devm_work_autocancel(dev, &st->trig_conv, ad4062_trigger_work); + if (ret) + return ret; + return devm_iio_device_register(dev, indio_dev); } From c894e05871b4ccd4b0829382aed2c586490c700f Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Wed, 17 Dec 2025 13:13:29 +0100 Subject: [PATCH 104/297] docs: iio: ad4062: Add IIO Events support Explains the IIO Events support. Signed-off-by: Jorge Marques Signed-off-by: Jonathan Cameron --- Documentation/iio/ad4062.rst | 41 ++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/Documentation/iio/ad4062.rst b/Documentation/iio/ad4062.rst index 12ac16dfc302..8d388f9e2f45 100644 --- a/Documentation/iio/ad4062.rst +++ b/Documentation/iio/ad4062.rst @@ -26,6 +26,7 @@ at the end of the read command. The two programmable GPIOS are optional and have a role assigned if present in the devicetree ``interrupt-names`` property: +- GP0: Is assigned the role of Threshold Either signal. - GP1: Is assigned the role of Data Ready signal. Device attributes @@ -74,8 +75,10 @@ Interrupts The interrupts are mapped through the ``interrupt-names`` and ``interrupts`` properties. -The ``interrupt-names`` ``gp1`` entry sets the role of Data Ready signal. -If it is not present, the driver fallback to enabling the same role as an +The ``interrupt-names`` ``gp0`` entry sets the role of Threshold signal, and +entry ``gp1`` the role of Data Ready signal. + +If each is not present, the driver fallback to enabling the same role as an I3C IBI. Low-power mode @@ -97,3 +100,37 @@ The acquisition is sequential and bounded by the protocol timings, software latency and internal timings, the sample rate is not configurable. The burst averaging mode does impact the effective sample rate, since it increases the internal timing to output a single sample. + +Threshold events +================ + +The ADC supports a monitoring mode to raise threshold events. The driver +supports a single interrupt for both rising and falling readings. + +The feature is enabled/disabled by setting ``thresh_either_en``. During monitor +mode, the device continuously operates in autonomous mode. Any register access +puts the device back in configuration mode, due to this, any access disables +monitor mode. + +The following event attributes are available: + +.. list-table:: Event attributes + :header-rows: 1 + + * - Attribute + - Description + * - ``sampling_frequency`` + - Frequency used in the monitoring mode, sets the device internal sample + rate when the mode is activated. + * - ``sampling_frequency_available`` + - List of available sample rates. + * - ``thresh_either_en`` + - Enable monitoring mode. + * - ``thresh_falling_hysteresis`` + - Set the hysteresis value for the minimum threshold. + * - ``thresh_falling_value`` + - Set the minimum threshold value. + * - ``thresh_rising_hysteresis`` + - Set the hysteresis value for the maximum threshold. + * - ``thresh_rising_value`` + - Set the maximum threshold value. From ba3a34b1f5cefd51b210659fa5861b23b17ebb57 Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Wed, 17 Dec 2025 13:13:30 +0100 Subject: [PATCH 105/297] iio: adc: ad4062: Add IIO Events support Adds support for IIO Events. Optionally, gp0 is assigned as Threshold Either signal, if not present, fallback to an I3C IBI with the same role. Signed-off-by: Jorge Marques Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad4062.c | 407 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 406 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/ad4062.c b/drivers/iio/adc/ad4062.c index afc8b6969cf0..2084f0058627 100644 --- a/drivers/iio/adc/ad4062.c +++ b/drivers/iio/adc/ad4062.c @@ -14,7 +14,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -51,14 +53,22 @@ #define AD4062_REG_ADC_CONFIG_SCALE_EN_MSK BIT(4) #define AD4062_REG_AVG_CONFIG 0x23 #define AD4062_REG_GP_CONF 0x24 +#define AD4062_REG_GP_CONF_MODE_MSK_0 GENMASK(2, 0) #define AD4062_REG_GP_CONF_MODE_MSK_1 GENMASK(6, 4) #define AD4062_REG_INTR_CONF 0x25 +#define AD4062_REG_INTR_CONF_EN_MSK_0 GENMASK(1, 0) #define AD4062_REG_INTR_CONF_EN_MSK_1 GENMASK(5, 4) #define AD4062_REG_TIMER_CONFIG 0x27 #define AD4062_REG_TIMER_CONFIG_FS_MASK GENMASK(7, 4) +#define AD4062_REG_MAX_LIMIT 0x29 +#define AD4062_REG_MIN_LIMIT 0x2B +#define AD4062_REG_MAX_HYST 0x2C +#define AD4062_REG_MIN_HYST 0x2D #define AD4062_REG_MON_VAL 0x2F #define AD4062_REG_ADC_IBI_EN 0x31 #define AD4062_REG_ADC_IBI_EN_CONV_TRIGGER BIT(2) +#define AD4062_REG_ADC_IBI_EN_MAX BIT(1) +#define AD4062_REG_ADC_IBI_EN_MIN BIT(0) #define AD4062_REG_FUSE_CRC 0x40 #define AD4062_REG_DEVICE_STATUS 0x41 #define AD4062_REG_DEVICE_STATUS_DEVICE_RESET BIT(6) @@ -78,9 +88,13 @@ #define AD4060_PROD_ID 0x7A #define AD4062_PROD_ID 0x7C +#define AD4062_GP_INTR 0x1 #define AD4062_GP_DRDY 0x2 +#define AD4062_LIMIT_BITS 12 + #define AD4062_INTR_EN_NEITHER 0x0 +#define AD4062_INTR_EN_EITHER 0x3 #define AD4062_TCONV_NS 270 @@ -149,10 +163,12 @@ struct ad4062_state { struct iio_dev *indio_dev; struct i3c_device *i3cdev; struct regmap *regmap; + bool wait_event; int vref_uV; unsigned int samp_freqs[ARRAY_SIZE(ad4062_conversion_freqs)]; bool gpo_irq[2]; u16 sampling_frequency; + u16 events_frequency; u8 oversamp_ratio; u8 conv_sizeof; u8 conv_addr; @@ -188,6 +204,26 @@ static const struct regmap_access_table ad4062_regmap_wr_table = { .n_yes_ranges = ARRAY_SIZE(ad4062_regmap_wr_ranges), }; +static const struct iio_event_spec ad4062_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_shared_by_all = BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_HYSTERESIS), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_HYSTERESIS), + }, +}; + #define AD4062_CHAN(bits) { \ .type = IIO_VOLTAGE, \ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_RAW) | \ @@ -199,6 +235,8 @@ static const struct regmap_access_table ad4062_regmap_wr_table = { .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ .indexed = 1, \ .channel = 0, \ + .event_spec = ad4062_events, \ + .num_event_specs = ARRAY_SIZE(ad4062_events), \ .has_ext_scan_type = 1, \ .ext_scan_type = ad4062_scan_type_##bits##_s, \ .num_ext_scan_type = ARRAY_SIZE(ad4062_scan_type_##bits##_s), \ @@ -218,6 +256,73 @@ static const struct ad4062_chip_info ad4062_chip_info = { .avg_max = 4096, }; +static ssize_t sampling_frequency_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ad4062_state *st = iio_priv(dev_to_iio_dev(dev)); + + return sysfs_emit(buf, "%d\n", ad4062_conversion_freqs[st->events_frequency]); +} + +static int sampling_frequency_store_dispatch(struct iio_dev *indio_dev, + const char *buf) +{ + struct ad4062_state *st = iio_priv(indio_dev); + int val, ret; + + if (st->wait_event) + return -EBUSY; + + ret = kstrtoint(buf, 10, &val); + if (ret) + return ret; + + st->events_frequency = find_closest_descending(val, ad4062_conversion_freqs, + ARRAY_SIZE(ad4062_conversion_freqs)); + return 0; +} + +static ssize_t sampling_frequency_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = sampling_frequency_store_dispatch(indio_dev, buf); + iio_device_release_direct(indio_dev); + return ret ?: len; +} + +static IIO_DEVICE_ATTR_RW(sampling_frequency, 0); + +static ssize_t sampling_frequency_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret = 0; + + for (u8 i = 0; i < ARRAY_SIZE(ad4062_conversion_freqs); i++) + ret += sysfs_emit_at(buf, ret, "%d%s", ad4062_conversion_freqs[i], + i != (ARRAY_SIZE(ad4062_conversion_freqs) - 1) ? " " : "\n"); + return ret; +} + +static IIO_DEVICE_ATTR_RO(sampling_frequency_available, 0); + +static struct attribute *ad4062_event_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad4062_event_attribute_group = { + .attrs = ad4062_event_attributes, +}; + static int ad4062_set_oversampling_ratio(struct ad4062_state *st, int val, int val2) { const u32 _max = st->chip->avg_max; @@ -344,9 +449,11 @@ static int ad4062_conversion_frequency_set(struct ad4062_state *st, u8 val) static int ad4062_set_operation_mode(struct ad4062_state *st, enum ad4062_operation_mode mode) { + const unsigned int samp_freq = mode == AD4062_MONITOR_MODE ? + st->events_frequency : st->sampling_frequency; int ret; - ret = ad4062_conversion_frequency_set(st, st->sampling_frequency); + ret = ad4062_conversion_frequency_set(st, samp_freq); if (ret) return ret; @@ -355,6 +462,17 @@ static int ad4062_set_operation_mode(struct ad4062_state *st, if (ret) return ret; + if (mode == AD4062_MONITOR_MODE) { + /* Change address pointer to enter monitor mode */ + struct i3c_priv_xfer xfer_trigger = { + .data.out = &st->conv_addr, + .len = sizeof(st->conv_addr), + .rnw = false, + }; + st->conv_addr = AD4062_REG_CONV_TRIGGER_32BITS; + return i3c_device_do_priv_xfers(st->i3cdev, &xfer_trigger, 1); + } + return regmap_write(st->regmap, AD4062_REG_MODE_SET, AD4062_REG_MODE_SET_ENTER_ADC); } @@ -385,6 +503,13 @@ static int ad4062_setup(struct iio_dev *indio_dev, struct iio_chan_spec const *c if (IS_ERR(scan_type)) return PTR_ERR(scan_type); + ret = regmap_update_bits(st->regmap, AD4062_REG_GP_CONF, + AD4062_REG_GP_CONF_MODE_MSK_0, + FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_0, + AD4062_GP_INTR)); + if (ret) + return ret; + ret = regmap_update_bits(st->regmap, AD4062_REG_GP_CONF, AD4062_REG_GP_CONF_MODE_MSK_1, FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_1, @@ -404,6 +529,13 @@ static int ad4062_setup(struct iio_dev *indio_dev, struct iio_chan_spec const *c if (ret) return ret; + ret = regmap_update_bits(st->regmap, AD4062_REG_INTR_CONF, + AD4062_REG_INTR_CONF_EN_MSK_0, + FIELD_PREP(AD4062_REG_INTR_CONF_EN_MSK_0, + AD4062_INTR_EN_EITHER)); + if (ret) + return ret; + ret = regmap_update_bits(st->regmap, AD4062_REG_INTR_CONF, AD4062_REG_INTR_CONF_EN_MSK_1, FIELD_PREP(AD4062_REG_INTR_CONF_EN_MSK_1, @@ -416,6 +548,19 @@ static int ad4062_setup(struct iio_dev *indio_dev, struct iio_chan_spec const *c &st->buf.be16, sizeof(st->buf.be16)); } +static irqreturn_t ad4062_irq_handler_thresh(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; +} + static irqreturn_t ad4062_irq_handler_drdy(int irq, void *private) { struct iio_dev *indio_dev = private; @@ -434,6 +579,14 @@ static void ad4062_ibi_handler(struct i3c_device *i3cdev, { struct ad4062_state *st = i3cdev_get_drvdata(i3cdev); + if (st->wait_event) { + iio_push_event(st->indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(st->indio_dev)); + return; + } if (iio_buffer_enabled(st->indio_dev)) iio_trigger_poll_nested(st->trigger); else @@ -529,6 +682,25 @@ static int ad4062_request_irq(struct iio_dev *indio_dev) struct device *dev = &st->i3cdev->dev; int ret; + ret = fwnode_irq_get_byname(dev_fwnode(&st->i3cdev->dev), "gp0"); + if (ret == -EPROBE_DEFER) + return ret; + + if (ret < 0) { + ret = regmap_update_bits(st->regmap, AD4062_REG_ADC_IBI_EN, + AD4062_REG_ADC_IBI_EN_MAX | AD4062_REG_ADC_IBI_EN_MIN, + AD4062_REG_ADC_IBI_EN_MAX | AD4062_REG_ADC_IBI_EN_MIN); + if (ret) + return ret; + } else { + ret = devm_request_threaded_irq(dev, ret, NULL, + ad4062_irq_handler_thresh, + IRQF_ONESHOT, indio_dev->name, + indio_dev); + if (ret) + return ret; + } + ret = fwnode_irq_get_byname(dev_fwnode(&st->i3cdev->dev), "gp1"); if (ret == -EPROBE_DEFER) return ret; @@ -720,6 +892,9 @@ static int ad4062_read_chan_raw(struct ad4062_state *st, int *val) static int ad4062_read_raw_dispatch(struct ad4062_state *st, int *val, int *val2, long info) { + if (st->wait_event) + return -EBUSY; + switch (info) { case IIO_CHAN_INFO_RAW: return ad4062_read_chan_raw(st, val); @@ -761,6 +936,9 @@ static int ad4062_read_raw(struct iio_dev *indio_dev, static int ad4062_write_raw_dispatch(struct ad4062_state *st, int val, int val2, long info) { + if (st->wait_event) + return -EBUSY; + switch (info) { case IIO_CHAN_INFO_OVERSAMPLING_RATIO: return ad4062_set_oversampling_ratio(st, val, val2); @@ -789,7 +967,224 @@ static int ad4062_write_raw(struct iio_dev *indio_dev, return -EBUSY; ret = ad4062_write_raw_dispatch(st, val, val2, info); + iio_device_release_direct(indio_dev); + return ret; +} +static int pm_ad4062_monitor_mode_enable(struct ad4062_state *st) +{ + int ret; + + PM_RUNTIME_ACQUIRE(&st->i3cdev->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + return ad4062_set_operation_mode(st, AD4062_MONITOR_MODE); +} + +static int ad4062_monitor_mode_enable(struct ad4062_state *st) +{ + int ret; + + ret = pm_ad4062_monitor_mode_enable(st); + if (ret) + return ret; + + pm_runtime_get_noresume(&st->i3cdev->dev); + return 0; +} + +static int ad4062_monitor_mode_disable(struct ad4062_state *st) +{ + pm_runtime_put_autosuspend(&st->i3cdev->dev); + return 0; +} + +static int ad4062_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ad4062_state *st = iio_priv(indio_dev); + + return st->wait_event; +} + +static int ad4062_write_event_config_dispatch(struct iio_dev *indio_dev, + bool state) +{ + struct ad4062_state *st = iio_priv(indio_dev); + int ret; + + if (st->wait_event == state) + ret = 0; + else if (state) + ret = ad4062_monitor_mode_enable(st); + else + ret = ad4062_monitor_mode_disable(st); + if (ret) + return ret; + + st->wait_event = state; + return 0; +} + +static int ad4062_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad4062_write_event_config_dispatch(indio_dev, state); + iio_device_release_direct(indio_dev); + return ret; +} + +static int __ad4062_read_event_info_value(struct ad4062_state *st, + enum iio_event_direction dir, int *val) +{ + int ret; + u8 reg; + + if (dir == IIO_EV_DIR_RISING) + reg = AD4062_REG_MAX_LIMIT; + else + reg = AD4062_REG_MIN_LIMIT; + + ret = regmap_bulk_read(st->regmap, reg, &st->buf.be16, + sizeof(st->buf.be16)); + if (ret) + return ret; + + *val = sign_extend32(be16_to_cpu(st->buf.be16), AD4062_LIMIT_BITS - 1); + + return 0; +} + +static int __ad4062_read_event_info_hysteresis(struct ad4062_state *st, + enum iio_event_direction dir, int *val) +{ + u8 reg; + + if (dir == IIO_EV_DIR_RISING) + reg = AD4062_REG_MAX_HYST; + else + reg = AD4062_REG_MIN_HYST; + return regmap_read(st->regmap, reg, val); +} + +static int ad4062_read_event_config_dispatch(struct iio_dev *indio_dev, + enum iio_event_direction dir, + enum iio_event_info info, int *val) +{ + struct ad4062_state *st = iio_priv(indio_dev); + + if (st->wait_event) + return -EBUSY; + + switch (info) { + case IIO_EV_INFO_VALUE: + return __ad4062_read_event_info_value(st, dir, val); + case IIO_EV_INFO_HYSTERESIS: + return __ad4062_read_event_info_hysteresis(st, dir, val); + default: + return -EINVAL; + } +} + +static int ad4062_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, + int *val2) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad4062_read_event_config_dispatch(indio_dev, dir, info, val); + iio_device_release_direct(indio_dev); + return ret ?: IIO_VAL_INT; +} + +static int __ad4062_write_event_info_value(struct ad4062_state *st, + enum iio_event_direction dir, int val) +{ + u8 reg; + + if (val != sign_extend32(val, AD4062_LIMIT_BITS - 1)) + return -EINVAL; + if (dir == IIO_EV_DIR_RISING) + reg = AD4062_REG_MAX_LIMIT; + else + reg = AD4062_REG_MIN_LIMIT; + st->buf.be16 = cpu_to_be16(val); + + return regmap_bulk_write(st->regmap, reg, &st->buf.be16, + sizeof(st->buf.be16)); +} + +static int __ad4062_write_event_info_hysteresis(struct ad4062_state *st, + enum iio_event_direction dir, int val) +{ + u8 reg; + + if (val > BIT(7) - 1) + return -EINVAL; + if (dir == IIO_EV_DIR_RISING) + reg = AD4062_REG_MAX_HYST; + else + reg = AD4062_REG_MIN_HYST; + + return regmap_write(st->regmap, reg, val); +} + +static int ad4062_write_event_value_dispatch(struct iio_dev *indio_dev, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val) +{ + struct ad4062_state *st = iio_priv(indio_dev); + + if (st->wait_event) + return -EBUSY; + + switch (type) { + case IIO_EV_TYPE_THRESH: + switch (info) { + case IIO_EV_INFO_VALUE: + return __ad4062_write_event_info_value(st, dir, val); + case IIO_EV_INFO_HYSTERESIS: + return __ad4062_write_event_info_hysteresis(st, dir, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ad4062_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, + int val2) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad4062_write_event_value_dispatch(indio_dev, type, dir, info, val); iio_device_release_direct(indio_dev); return ret; } @@ -825,6 +1220,9 @@ static int pm_ad4062_triggered_buffer_postenable(struct ad4062_state *st) if (ret) return ret; + if (st->wait_event) + return -EBUSY; + ret = ad4062_set_operation_mode(st, st->mode); if (ret) return ret; @@ -900,6 +1298,11 @@ static const struct iio_info ad4062_info = { .read_raw = ad4062_read_raw, .write_raw = ad4062_write_raw, .read_avail = ad4062_read_avail, + .read_event_config = ad4062_read_event_config, + .write_event_config = ad4062_write_event_config, + .read_event_value = ad4062_read_event_value, + .write_event_value = ad4062_write_event_value, + .event_attrs = &ad4062_event_attribute_group, .get_current_scan_type = ad4062_get_current_scan_type, .debugfs_reg_access = ad4062_debugfs_reg_access, }; @@ -980,8 +1383,10 @@ static int ad4062_probe(struct i3c_device *i3cdev) "Failed to initialize regmap\n"); st->mode = AD4062_SAMPLE_MODE; + st->wait_event = false; st->chip = chip; st->sampling_frequency = 0; + st->events_frequency = 0; st->oversamp_ratio = 0; st->indio_dev = indio_dev; From d2ca7af298fe4c373bce2d6714649b887f6426d8 Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Wed, 17 Dec 2025 13:13:31 +0100 Subject: [PATCH 106/297] docs: iio: ad4062: Add GPIO Controller support Explains the GPIO controller support with emphasis on the mask depending on which GPs are exposed. Signed-off-by: Jorge Marques Reviewed-by: Linus Walleij Signed-off-by: Jonathan Cameron --- Documentation/iio/ad4062.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Documentation/iio/ad4062.rst b/Documentation/iio/ad4062.rst index 8d388f9e2f45..d77287836430 100644 --- a/Documentation/iio/ad4062.rst +++ b/Documentation/iio/ad4062.rst @@ -29,6 +29,9 @@ the devicetree ``interrupt-names`` property: - GP0: Is assigned the role of Threshold Either signal. - GP1: Is assigned the role of Data Ready signal. +If the property ``gpio-controller`` is present in the devicetree, then the GPO +not present in the ``interrupt-names`` is exposed as a GPO. + Device attributes ================= @@ -134,3 +137,12 @@ The following event attributes are available: - Set the hysteresis value for the maximum threshold. * - ``thresh_rising_value`` - Set the maximum threshold value. + +GPO controller support +====================== + +The device supports using GP0 and GP1 as GPOs. If the devicetree contains the +node ``gpio-controller```, the device is marked as a GPIO controller and the +GPs not listed in ``interrupt-names`` are exposed as a GPO. The GPIO index +matches the pin name, so if GP0 is not exposed but GP1 is, index 0 is masked +out and only index 1 can be set. From da1d3596b1e456b5d5d5f719bbf5ce562f4fe22d Mon Sep 17 00:00:00 2001 From: Jorge Marques Date: Wed, 17 Dec 2025 13:13:32 +0100 Subject: [PATCH 107/297] iio: adc: ad4062: Add GPIO Controller support When gp0 or gp1 is not taken as an interrupt, expose them as GPO if gpio-contoller is set in the devicetree. gpio-regmap is not used because the GPO static low is 'b101 and static high is 0b110; low state requires setting bit 0, not fitting the abstraction of low=0 and high=mask. Signed-off-by: Jorge Marques Reviewed-by: Linus Walleij Acked-by: Bartosz Golaszewski Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad4062.c | 125 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/drivers/iio/adc/ad4062.c b/drivers/iio/adc/ad4062.c index 2084f0058627..a6b3ccc98acf 100644 --- a/drivers/iio/adc/ad4062.c +++ b/drivers/iio/adc/ad4062.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -88,8 +89,11 @@ #define AD4060_PROD_ID 0x7A #define AD4062_PROD_ID 0x7C +#define AD4062_GP_DISABLED 0x0 #define AD4062_GP_INTR 0x1 #define AD4062_GP_DRDY 0x2 +#define AD4062_GP_STATIC_LOW 0x5 +#define AD4062_GP_STATIC_HIGH 0x6 #define AD4062_LIMIT_BITS 12 @@ -687,12 +691,14 @@ static int ad4062_request_irq(struct iio_dev *indio_dev) return ret; if (ret < 0) { + st->gpo_irq[0] = false; ret = regmap_update_bits(st->regmap, AD4062_REG_ADC_IBI_EN, AD4062_REG_ADC_IBI_EN_MAX | AD4062_REG_ADC_IBI_EN_MIN, AD4062_REG_ADC_IBI_EN_MAX | AD4062_REG_ADC_IBI_EN_MIN); if (ret) return ret; } else { + st->gpo_irq[0] = true; ret = devm_request_threaded_irq(dev, ret, NULL, ad4062_irq_handler_thresh, IRQF_ONESHOT, indio_dev->name, @@ -1347,6 +1353,121 @@ static int ad4062_regulators_get(struct ad4062_state *st, bool *ref_sel) return 0; } +static int ad4062_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + return GPIO_LINE_DIRECTION_OUT; +} + +static int ad4062_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct ad4062_state *st = gpiochip_get_data(gc); + unsigned int reg_val = value ? AD4062_GP_STATIC_HIGH : AD4062_GP_STATIC_LOW; + + if (offset) + return regmap_update_bits(st->regmap, AD4062_REG_GP_CONF, + AD4062_REG_GP_CONF_MODE_MSK_1, + FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_1, reg_val)); + else + return regmap_update_bits(st->regmap, AD4062_REG_GP_CONF, + AD4062_REG_GP_CONF_MODE_MSK_0, + FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_0, reg_val)); +} + +static int ad4062_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct ad4062_state *st = gpiochip_get_data(gc); + unsigned int reg_val; + int ret; + + ret = regmap_read(st->regmap, AD4062_REG_GP_CONF, ®_val); + if (ret) + return ret; + + if (offset) + reg_val = FIELD_GET(AD4062_REG_GP_CONF_MODE_MSK_1, reg_val); + else + reg_val = FIELD_GET(AD4062_REG_GP_CONF_MODE_MSK_0, reg_val); + + return reg_val == AD4062_GP_STATIC_HIGH; +} + +static void ad4062_gpio_disable(void *data) +{ + struct ad4062_state *st = data; + u8 val = FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_0, AD4062_GP_DISABLED) | + FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_1, AD4062_GP_DISABLED); + + regmap_update_bits(st->regmap, AD4062_REG_GP_CONF, + AD4062_REG_GP_CONF_MODE_MSK_1 | AD4062_REG_GP_CONF_MODE_MSK_0, + val); +} + +static int ad4062_gpio_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct ad4062_state *st = gpiochip_get_data(gc); + + bitmap_zero(valid_mask, ngpios); + + for (unsigned int i = 0; i < ARRAY_SIZE(st->gpo_irq); i++) + __assign_bit(i, valid_mask, !st->gpo_irq[i]); + + return 0; +} + +static int ad4062_gpio_init(struct ad4062_state *st) +{ + struct device *dev = &st->i3cdev->dev; + struct gpio_chip *gc; + u8 val, mask; + int ret; + + if (!device_property_read_bool(dev, "gpio-controller")) + return 0; + + gc = devm_kzalloc(dev, sizeof(*gc), GFP_KERNEL); + if (!gc) + return -ENOMEM; + + val = 0; + mask = 0; + if (!st->gpo_irq[0]) { + mask |= AD4062_REG_GP_CONF_MODE_MSK_0; + val |= FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_0, AD4062_GP_STATIC_LOW); + } + if (!st->gpo_irq[1]) { + mask |= AD4062_REG_GP_CONF_MODE_MSK_1; + val |= FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_1, AD4062_GP_STATIC_LOW); + } + + ret = regmap_update_bits(st->regmap, AD4062_REG_GP_CONF, + mask, val); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, ad4062_gpio_disable, st); + if (ret) + return ret; + + gc->parent = dev; + gc->label = st->chip->name; + gc->owner = THIS_MODULE; + gc->base = -1; + gc->ngpio = 2; + gc->init_valid_mask = ad4062_gpio_init_valid_mask; + gc->get_direction = ad4062_gpio_get_direction; + gc->set = ad4062_gpio_set; + gc->get = ad4062_gpio_get; + gc->can_sleep = true; + + ret = devm_gpiochip_add_data(dev, gc, st); + if (ret) + return dev_err_probe(dev, ret, "Unable to register GPIO chip\n"); + + return 0; +} + static const struct i3c_device_id ad4062_id_table[] = { I3C_DEVICE(AD4062_I3C_VENDOR, AD4060_PROD_ID, &ad4060_chip_info), I3C_DEVICE(AD4062_I3C_VENDOR, AD4062_PROD_ID, &ad4062_chip_info), @@ -1435,6 +1556,10 @@ static int ad4062_probe(struct i3c_device *i3cdev) if (ret) return dev_err_probe(dev, ret, "Failed to request i3c ibi\n"); + ret = ad4062_gpio_init(st); + if (ret) + return ret; + ret = devm_work_autocancel(dev, &st->trig_conv, ad4062_trigger_work); if (ret) return ret; From 48de61f6c135ec03d9a1d644411ea501f7d49dff Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 21 Dec 2025 15:26:03 +0100 Subject: [PATCH 108/297] iio: adc: aspeed: Simplify probe() with local 'dev' and 'np' Simplify the probe function by using local 'dev' and 'np' variables instead of full pointer dereferences. This makes several lines shorter, which allows to avoid wrapping making code more readable. While touching the return line, simplify by avoiding unnecessary 'ret' assignment. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Jonathan Cameron --- drivers/iio/adc/aspeed_adc.c | 42 ++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/drivers/iio/adc/aspeed_adc.c b/drivers/iio/adc/aspeed_adc.c index 1ae45fe90e6c..4be44c524b4d 100644 --- a/drivers/iio/adc/aspeed_adc.c +++ b/drivers/iio/adc/aspeed_adc.c @@ -472,16 +472,18 @@ static int aspeed_adc_probe(struct platform_device *pdev) struct aspeed_adc_data *data; int ret; u32 adc_engine_control_reg_val; + struct device *dev = &pdev->dev; + struct device_node *np = dev_of_node(dev); unsigned long scaler_flags = 0; char clk_name[32], clk_parent_name[32]; - indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*data)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; data = iio_priv(indio_dev); - data->dev = &pdev->dev; - data->model_data = of_device_get_match_data(&pdev->dev); + data->dev = dev; + data->model_data = of_device_get_match_data(dev); platform_set_drvdata(pdev, indio_dev); data->base = devm_platform_ioremap_resource(pdev, 0); @@ -491,16 +493,15 @@ static int aspeed_adc_probe(struct platform_device *pdev) /* Register ADC clock prescaler with source specified by device tree. */ spin_lock_init(&data->clk_lock); snprintf(clk_parent_name, ARRAY_SIZE(clk_parent_name), "%s", - of_clk_get_parent_name(pdev->dev.of_node, 0)); + of_clk_get_parent_name(np, 0)); snprintf(clk_name, ARRAY_SIZE(clk_name), "%s-fixed-div", data->model_data->model_name); - data->fixed_div_clk = clk_hw_register_fixed_factor( - &pdev->dev, clk_name, clk_parent_name, 0, 1, 2); + data->fixed_div_clk = clk_hw_register_fixed_factor(dev, clk_name, + clk_parent_name, 0, 1, 2); if (IS_ERR(data->fixed_div_clk)) return PTR_ERR(data->fixed_div_clk); - ret = devm_add_action_or_reset(data->dev, - aspeed_adc_unregister_fixed_divider, + ret = devm_add_action_or_reset(dev, aspeed_adc_unregister_fixed_divider, data->fixed_div_clk); if (ret) return ret; @@ -510,7 +511,7 @@ static int aspeed_adc_probe(struct platform_device *pdev) snprintf(clk_name, ARRAY_SIZE(clk_name), "%s-prescaler", data->model_data->model_name); data->clk_prescaler = devm_clk_hw_register_divider( - &pdev->dev, clk_name, clk_parent_name, 0, + dev, clk_name, clk_parent_name, 0, data->base + ASPEED_REG_CLOCK_CONTROL, 17, 15, 0, &data->clk_lock); if (IS_ERR(data->clk_prescaler)) @@ -526,7 +527,7 @@ static int aspeed_adc_probe(struct platform_device *pdev) snprintf(clk_name, ARRAY_SIZE(clk_name), "%s-scaler", data->model_data->model_name); data->clk_scaler = devm_clk_hw_register_divider( - &pdev->dev, clk_name, clk_parent_name, scaler_flags, + dev, clk_name, clk_parent_name, scaler_flags, data->base + ASPEED_REG_CLOCK_CONTROL, 0, data->model_data->scaler_bit_width, data->model_data->need_prescaler ? CLK_DIVIDER_ONE_BASED : 0, @@ -534,15 +535,14 @@ static int aspeed_adc_probe(struct platform_device *pdev) if (IS_ERR(data->clk_scaler)) return PTR_ERR(data->clk_scaler); - data->rst = devm_reset_control_get_shared(&pdev->dev, NULL); + data->rst = devm_reset_control_get_shared(dev, NULL); if (IS_ERR(data->rst)) - return dev_err_probe(&pdev->dev, PTR_ERR(data->rst), + return dev_err_probe(dev, PTR_ERR(data->rst), "invalid or missing reset controller device tree entry"); reset_control_deassert(data->rst); - ret = devm_add_action_or_reset(data->dev, aspeed_adc_reset_assert, - data->rst); + ret = devm_add_action_or_reset(dev, aspeed_adc_reset_assert, data->rst); if (ret) return ret; @@ -554,7 +554,7 @@ static int aspeed_adc_probe(struct platform_device *pdev) if (ret) return ret; - if (of_property_present(data->dev->of_node, "aspeed,battery-sensing")) { + if (of_property_present(np, "aspeed,battery-sensing")) { if (data->model_data->bat_sense_sup) { data->battery_sensing = 1; if (readl(data->base + ASPEED_REG_ENGINE_CONTROL) & @@ -566,15 +566,13 @@ static int aspeed_adc_probe(struct platform_device *pdev) data->battery_mode_gain.div = 2; } } else - dev_warn(&pdev->dev, - "Failed to enable battery-sensing mode\n"); + dev_warn(dev, "Failed to enable battery-sensing mode\n"); } ret = clk_prepare_enable(data->clk_scaler->clk); if (ret) return ret; - ret = devm_add_action_or_reset(data->dev, - aspeed_adc_clk_disable_unprepare, + ret = devm_add_action_or_reset(dev, aspeed_adc_clk_disable_unprepare, data->clk_scaler->clk); if (ret) return ret; @@ -592,8 +590,7 @@ static int aspeed_adc_probe(struct platform_device *pdev) writel(adc_engine_control_reg_val, data->base + ASPEED_REG_ENGINE_CONTROL); - ret = devm_add_action_or_reset(data->dev, aspeed_adc_power_down, - data); + ret = devm_add_action_or_reset(dev, aspeed_adc_power_down, data); if (ret) return ret; @@ -625,8 +622,7 @@ static int aspeed_adc_probe(struct platform_device *pdev) aspeed_adc_iio_channels; indio_dev->num_channels = data->model_data->num_channels; - ret = devm_iio_device_register(data->dev, indio_dev); - return ret; + return devm_iio_device_register(dev, indio_dev); } static const struct aspeed_adc_trim_locate ast2500_adc_trim = { From 5b758ebc3d59909c6fd75b7b2523ec033c7c415c Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 21 Dec 2025 15:26:04 +0100 Subject: [PATCH 109/297] iio: adc: exynos: Simplify probe() with local 'dev' and 'np' Simplify the probe function by using local 'dev' and 'np' variables instead of full pointer dereferences. This makes several lines shorter, which allows to avoid wrapping making code more readable. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Jonathan Cameron --- drivers/iio/adc/exynos_adc.c | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/drivers/iio/adc/exynos_adc.c b/drivers/iio/adc/exynos_adc.c index 491e8dcfd91e..aa287132a369 100644 --- a/drivers/iio/adc/exynos_adc.c +++ b/drivers/iio/adc/exynos_adc.c @@ -552,12 +552,13 @@ static int exynos_adc_remove_devices(struct device *dev, void *c) static int exynos_adc_probe(struct platform_device *pdev) { struct exynos_adc *info = NULL; + struct device *dev = &pdev->dev; struct device_node *np = pdev->dev.of_node; struct iio_dev *indio_dev = NULL; int ret; int irq; - indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct exynos_adc)); + indio_dev = devm_iio_device_alloc(dev, sizeof(struct exynos_adc)); if (!indio_dev) return -ENOMEM; @@ -565,7 +566,7 @@ static int exynos_adc_probe(struct platform_device *pdev) info->data = exynos_adc_get_data(pdev); if (!info->data) - return dev_err_probe(&pdev->dev, -EINVAL, "failed getting exynos_adc_data\n"); + return dev_err_probe(dev, -EINVAL, "failed getting exynos_adc_data\n"); info->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(info->regs)) @@ -573,11 +574,9 @@ static int exynos_adc_probe(struct platform_device *pdev) if (info->data->needs_adc_phy) { - info->pmu_map = syscon_regmap_lookup_by_phandle( - pdev->dev.of_node, - "samsung,syscon-phandle"); + info->pmu_map = syscon_regmap_lookup_by_phandle(np, "samsung,syscon-phandle"); if (IS_ERR(info->pmu_map)) - return dev_err_probe(&pdev->dev, PTR_ERR(info->pmu_map), + return dev_err_probe(dev, PTR_ERR(info->pmu_map), "syscon regmap lookup failed.\n"); } @@ -585,25 +584,24 @@ static int exynos_adc_probe(struct platform_device *pdev) if (irq < 0) return irq; info->irq = irq; - info->dev = &pdev->dev; + info->dev = dev; init_completion(&info->completion); - info->clk = devm_clk_get(&pdev->dev, "adc"); + info->clk = devm_clk_get(dev, "adc"); if (IS_ERR(info->clk)) - return dev_err_probe(&pdev->dev, PTR_ERR(info->clk), "failed getting clock\n"); + return dev_err_probe(dev, PTR_ERR(info->clk), "failed getting clock\n"); if (info->data->needs_sclk) { - info->sclk = devm_clk_get(&pdev->dev, "sclk"); + info->sclk = devm_clk_get(dev, "sclk"); if (IS_ERR(info->sclk)) - return dev_err_probe(&pdev->dev, PTR_ERR(info->sclk), + return dev_err_probe(dev, PTR_ERR(info->sclk), "failed getting sclk clock\n"); } - info->vdd = devm_regulator_get(&pdev->dev, "vdd"); + info->vdd = devm_regulator_get(dev, "vdd"); if (IS_ERR(info->vdd)) - return dev_err_probe(&pdev->dev, PTR_ERR(info->vdd), - "failed getting regulator"); + return dev_err_probe(dev, PTR_ERR(info->vdd), "failed getting regulator"); ret = regulator_enable(info->vdd); if (ret) @@ -619,7 +617,7 @@ static int exynos_adc_probe(struct platform_device *pdev) platform_set_drvdata(pdev, indio_dev); - indio_dev->name = dev_name(&pdev->dev); + indio_dev->name = dev_name(dev); indio_dev->info = &exynos_adc_iio_info; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = exynos_adc_iio_channels; @@ -627,11 +625,9 @@ static int exynos_adc_probe(struct platform_device *pdev) mutex_init(&info->lock); - ret = request_irq(info->irq, exynos_adc_isr, - 0, dev_name(&pdev->dev), info); + ret = request_irq(info->irq, exynos_adc_isr, 0, dev_name(dev), info); if (ret < 0) { - dev_err(&pdev->dev, "failed requesting irq, irq = %d\n", - info->irq); + dev_err(dev, "failed requesting irq, irq = %d\n", info->irq); goto err_disable_clk; } @@ -644,7 +640,7 @@ static int exynos_adc_probe(struct platform_device *pdev) ret = of_platform_populate(np, exynos_adc_match, NULL, &indio_dev->dev); if (ret < 0) { - dev_err(&pdev->dev, "failed adding child nodes\n"); + dev_err(dev, "failed adding child nodes\n"); goto err_of_populate; } From d8011335512f105b38455f2ba4f5589335120261 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 21 Dec 2025 15:26:05 +0100 Subject: [PATCH 110/297] iio: adc: rockchip: Simplify probe() with local 'dev' Simplify the probe function by using a local 'dev' variable instead of full pointer dereference. This makes several lines shorter, which allows to avoid wrapping making code more readable. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Heiko Stuebner Signed-off-by: Jonathan Cameron --- drivers/iio/adc/rockchip_saradc.c | 50 ++++++++++++++----------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/drivers/iio/adc/rockchip_saradc.c b/drivers/iio/adc/rockchip_saradc.c index 263d80c5fc50..0f0bf2906af0 100644 --- a/drivers/iio/adc/rockchip_saradc.c +++ b/drivers/iio/adc/rockchip_saradc.c @@ -456,6 +456,7 @@ static int rockchip_saradc_probe(struct platform_device *pdev) { const struct rockchip_saradc_data *match_data; struct rockchip_saradc *info = NULL; + struct device *dev = &pdev->dev; struct device_node *np = pdev->dev.of_node; struct iio_dev *indio_dev = NULL; int ret; @@ -464,23 +465,21 @@ static int rockchip_saradc_probe(struct platform_device *pdev) if (!np) return -ENODEV; - indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); if (!indio_dev) return -ENOMEM; info = iio_priv(indio_dev); - match_data = of_device_get_match_data(&pdev->dev); + match_data = of_device_get_match_data(dev); if (!match_data) - return dev_err_probe(&pdev->dev, -ENODEV, - "failed to match device\n"); + return dev_err_probe(dev, -ENODEV, "failed to match device\n"); info->data = match_data; /* Sanity check for possible later IP variants with more channels */ if (info->data->num_channels > SARADC_MAX_CHANNELS) - return dev_err_probe(&pdev->dev, -EINVAL, - "max channels exceeded"); + return dev_err_probe(dev, -EINVAL, "max channels exceeded"); info->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(info->regs)) @@ -490,10 +489,9 @@ static int rockchip_saradc_probe(struct platform_device *pdev) * The reset should be an optional property, as it should work * with old devicetrees as well */ - info->reset = devm_reset_control_get_optional_exclusive(&pdev->dev, - "saradc-apb"); + info->reset = devm_reset_control_get_optional_exclusive(dev, "saradc-apb"); if (IS_ERR(info->reset)) - return dev_err_probe(&pdev->dev, PTR_ERR(info->reset), + return dev_err_probe(dev, PTR_ERR(info->reset), "failed to get saradc-apb\n"); init_completion(&info->completion); @@ -502,14 +500,14 @@ static int rockchip_saradc_probe(struct platform_device *pdev) if (irq < 0) return irq; - ret = devm_request_irq(&pdev->dev, irq, rockchip_saradc_isr, + ret = devm_request_irq(dev, irq, rockchip_saradc_isr, 0, dev_name(&pdev->dev), info); if (ret < 0) - return dev_err_probe(&pdev->dev, ret, "failed requesting irq %d\n", irq); + return dev_err_probe(dev, ret, "failed requesting irq %d\n", irq); - info->vref = devm_regulator_get(&pdev->dev, "vref"); + info->vref = devm_regulator_get(dev, "vref"); if (IS_ERR(info->vref)) - return dev_err_probe(&pdev->dev, PTR_ERR(info->vref), + return dev_err_probe(dev, PTR_ERR(info->vref), "failed to get regulator\n"); if (info->reset) @@ -517,11 +515,9 @@ static int rockchip_saradc_probe(struct platform_device *pdev) ret = regulator_enable(info->vref); if (ret < 0) - return dev_err_probe(&pdev->dev, ret, - "failed to enable vref regulator\n"); + return dev_err_probe(dev, ret, "failed to enable vref regulator\n"); - ret = devm_add_action_or_reset(&pdev->dev, - rockchip_saradc_regulator_disable, info); + ret = devm_add_action_or_reset(dev, rockchip_saradc_regulator_disable, info); if (ret) return ret; @@ -531,14 +527,13 @@ static int rockchip_saradc_probe(struct platform_device *pdev) info->uv_vref = ret; - info->pclk = devm_clk_get_enabled(&pdev->dev, "apb_pclk"); + info->pclk = devm_clk_get_enabled(dev, "apb_pclk"); if (IS_ERR(info->pclk)) - return dev_err_probe(&pdev->dev, PTR_ERR(info->pclk), - "failed to get pclk\n"); + return dev_err_probe(dev, PTR_ERR(info->pclk), "failed to get pclk\n"); - info->clk = devm_clk_get_enabled(&pdev->dev, "saradc"); + info->clk = devm_clk_get_enabled(dev, "saradc"); if (IS_ERR(info->clk)) - return dev_err_probe(&pdev->dev, PTR_ERR(info->clk), + return dev_err_probe(dev, PTR_ERR(info->clk), "failed to get adc clock\n"); /* * Use a default value for the converter clock. @@ -546,18 +541,17 @@ static int rockchip_saradc_probe(struct platform_device *pdev) */ ret = clk_set_rate(info->clk, info->data->clk_rate); if (ret < 0) - return dev_err_probe(&pdev->dev, ret, - "failed to set adc clk rate\n"); + return dev_err_probe(dev, ret, "failed to set adc clk rate\n"); platform_set_drvdata(pdev, indio_dev); - indio_dev->name = dev_name(&pdev->dev); + indio_dev->name = dev_name(dev); indio_dev->info = &rockchip_saradc_iio_info; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = info->data->channels; indio_dev->num_channels = info->data->num_channels; - ret = devm_iio_triggered_buffer_setup(&indio_dev->dev, indio_dev, NULL, + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, rockchip_saradc_trigger_handler, NULL); if (ret) @@ -568,7 +562,7 @@ static int rockchip_saradc_probe(struct platform_device *pdev) if (ret) return ret; - ret = devm_add_action_or_reset(&pdev->dev, + ret = devm_add_action_or_reset(dev, rockchip_saradc_regulator_unreg_notifier, info); if (ret) @@ -576,7 +570,7 @@ static int rockchip_saradc_probe(struct platform_device *pdev) mutex_init(&info->lock); - return devm_iio_device_register(&pdev->dev, indio_dev); + return devm_iio_device_register(dev, indio_dev); } static int rockchip_saradc_suspend(struct device *dev) From fe1846f61a82645f94d13ef2e6751f6cf69c60de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Mon, 22 Dec 2025 13:48:01 +0000 Subject: [PATCH 111/297] iio: dac: adi-axi-dac: Make use of a local struct device variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use a local struct device variable to improve readability in some code paths during probe. While at it, fix some line breaks not properly aligned to the open parenthesis. Signed-off-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/dac/adi-axi-dac.c | 47 +++++++++++++++++------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/drivers/iio/dac/adi-axi-dac.c b/drivers/iio/dac/adi-axi-dac.c index 0d525272a8a8..851d837d7ced 100644 --- a/drivers/iio/dac/adi-axi-dac.c +++ b/drivers/iio/dac/adi-axi-dac.c @@ -885,34 +885,35 @@ static const struct regmap_config axi_dac_regmap_config = { static int axi_dac_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; struct axi_dac_state *st; void __iomem *base; unsigned int ver; struct clk *clk; int ret; - st = devm_kzalloc(&pdev->dev, sizeof(*st), GFP_KERNEL); + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); if (!st) return -ENOMEM; - st->info = device_get_match_data(&pdev->dev); + st->info = device_get_match_data(dev); if (!st->info) return -ENODEV; - clk = devm_clk_get_enabled(&pdev->dev, "s_axi_aclk"); + clk = devm_clk_get_enabled(dev, "s_axi_aclk"); if (IS_ERR(clk)) { /* Backward compat., old fdt versions without clock-names. */ - clk = devm_clk_get_enabled(&pdev->dev, NULL); + clk = devm_clk_get_enabled(dev, NULL); if (IS_ERR(clk)) - return dev_err_probe(&pdev->dev, PTR_ERR(clk), - "failed to get clock\n"); + return dev_err_probe(dev, PTR_ERR(clk), + "failed to get clock\n"); } if (st->info->has_dac_clk) { struct clk *dac_clk; - dac_clk = devm_clk_get_enabled(&pdev->dev, "dac_clk"); + dac_clk = devm_clk_get_enabled(dev, "dac_clk"); if (IS_ERR(dac_clk)) - return dev_err_probe(&pdev->dev, PTR_ERR(dac_clk), + return dev_err_probe(dev, PTR_ERR(dac_clk), "failed to get dac_clk clock\n"); /* We only care about the streaming mode rate */ @@ -923,11 +924,10 @@ static int axi_dac_probe(struct platform_device *pdev) if (IS_ERR(base)) return PTR_ERR(base); - st->dev = &pdev->dev; - st->regmap = devm_regmap_init_mmio(&pdev->dev, base, - &axi_dac_regmap_config); + st->dev = dev; + st->regmap = devm_regmap_init_mmio(dev, base, &axi_dac_regmap_config); if (IS_ERR(st->regmap)) - return dev_err_probe(&pdev->dev, PTR_ERR(st->regmap), + return dev_err_probe(dev, PTR_ERR(st->regmap), "failed to init register map\n"); /* @@ -944,7 +944,7 @@ static int axi_dac_probe(struct platform_device *pdev) if (ADI_AXI_PCORE_VER_MAJOR(ver) != ADI_AXI_PCORE_VER_MAJOR(st->info->version)) { - dev_err(&pdev->dev, + dev_err(dev, "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n", ADI_AXI_PCORE_VER_MAJOR(st->info->version), ADI_AXI_PCORE_VER_MINOR(st->info->version), @@ -975,34 +975,33 @@ static int axi_dac_probe(struct platform_device *pdev) mutex_init(&st->lock); - ret = devm_iio_backend_register(&pdev->dev, st->info->backend_info, st); + ret = devm_iio_backend_register(dev, st->info->backend_info, st); if (ret) - return dev_err_probe(&pdev->dev, ret, + return dev_err_probe(dev, ret, "failed to register iio backend\n"); - device_for_each_child_node_scoped(&pdev->dev, child) { + device_for_each_child_node_scoped(dev, child) { int val; if (!st->info->has_child_nodes) - return dev_err_probe(&pdev->dev, -EINVAL, + return dev_err_probe(dev, -EINVAL, "invalid fdt axi-dac compatible."); /* Processing only reg 0 node */ ret = fwnode_property_read_u32(child, "reg", &val); if (ret) - return dev_err_probe(&pdev->dev, ret, - "invalid reg property."); + return dev_err_probe(dev, ret, "invalid reg property."); if (val != 0) - return dev_err_probe(&pdev->dev, -EINVAL, - "invalid node address."); + return dev_err_probe(dev, -EINVAL, + "invalid node address."); ret = axi_dac_create_platform_device(st, child); if (ret) - return dev_err_probe(&pdev->dev, -EINVAL, - "cannot create device."); + return dev_err_probe(dev, -EINVAL, + "cannot create device."); } - dev_info(&pdev->dev, "AXI DAC IP core (%d.%.2d.%c) probed\n", + dev_info(dev, "AXI DAC IP core (%d.%.2d.%c) probed\n", ADI_AXI_PCORE_VER_MAJOR(ver), ADI_AXI_PCORE_VER_MINOR(ver), ADI_AXI_PCORE_VER_PATCH(ver)); From 976df66573f32cea9f02f1f569207b68f08d4ef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Mon, 22 Dec 2025 13:48:02 +0000 Subject: [PATCH 112/297] iio: dac: adi-axi-dac: Make use of dev_err_probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Be consistent and use dev_err_probe() as in all other places in the .probe() path. While at it, remove the line break in the version condition. Yes, it goes over the 80 column limit but I do think the line break hurts readability in this case. Signed-off-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/dac/adi-axi-dac.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/drivers/iio/dac/adi-axi-dac.c b/drivers/iio/dac/adi-axi-dac.c index 851d837d7ced..9cc895bbe51a 100644 --- a/drivers/iio/dac/adi-axi-dac.c +++ b/drivers/iio/dac/adi-axi-dac.c @@ -942,18 +942,15 @@ static int axi_dac_probe(struct platform_device *pdev) if (ret) return ret; - if (ADI_AXI_PCORE_VER_MAJOR(ver) != - ADI_AXI_PCORE_VER_MAJOR(st->info->version)) { - dev_err(dev, - "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n", - ADI_AXI_PCORE_VER_MAJOR(st->info->version), - ADI_AXI_PCORE_VER_MINOR(st->info->version), - ADI_AXI_PCORE_VER_PATCH(st->info->version), - ADI_AXI_PCORE_VER_MAJOR(ver), - ADI_AXI_PCORE_VER_MINOR(ver), - ADI_AXI_PCORE_VER_PATCH(ver)); - return -ENODEV; - } + if (ADI_AXI_PCORE_VER_MAJOR(ver) != ADI_AXI_PCORE_VER_MAJOR(st->info->version)) + return dev_err_probe(dev, -ENODEV, + "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n", + ADI_AXI_PCORE_VER_MAJOR(st->info->version), + ADI_AXI_PCORE_VER_MINOR(st->info->version), + ADI_AXI_PCORE_VER_PATCH(st->info->version), + ADI_AXI_PCORE_VER_MAJOR(ver), + ADI_AXI_PCORE_VER_MINOR(ver), + ADI_AXI_PCORE_VER_PATCH(ver)); /* Let's get the core read only configuration */ ret = regmap_read(st->regmap, AXI_DAC_CONFIG_REG, &st->reg_config); From d63d868b312478523670b76007dcc5eaedc3ee07 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 27 Dec 2025 23:10:29 -0800 Subject: [PATCH 113/297] iio: test: drop dangling symbol in gain-time-scale helpers The code for this never went upstream. It was replaced by other code, so this should be dropped. Link: https://bugzilla.kernel.org/show_bug.cgi?id=216748 Fixes: cf996f039679 ("iio: test: test gain-time-scale helpers") Signed-off-by: Randy Dunlap Signed-off-by: Jonathan Cameron --- drivers/iio/test/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/iio/test/Kconfig b/drivers/iio/test/Kconfig index 6e65e929791c..4fc17dd0dcd7 100644 --- a/drivers/iio/test/Kconfig +++ b/drivers/iio/test/Kconfig @@ -8,7 +8,6 @@ config IIO_GTS_KUNIT_TEST tristate "Test IIO gain-time-scale helpers" if !KUNIT_ALL_TESTS depends on KUNIT select IIO_GTS_HELPER - select TEST_KUNIT_DEVICE_HELPERS default KUNIT_ALL_TESTS help build unit tests for the IIO light sensor gain-time-scale helpers. From d09ba52bfb25ea2ad50349b8de1986681afd6940 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Mon, 29 Dec 2025 12:27:43 +0100 Subject: [PATCH 114/297] iio: core: Constify struct configfs_item_operations and configfs_group_operations 'struct configfs_item_operations' and 'configfs_group_operations' are not modified in this driver. Constifying these structures moves some data to a read-only section, so increases overall security, especially when the structure holds some function pointers. On a x86_64, with allmodconfig: Before: ====== text data bss dec hex filename 5037 1528 64 6629 19e5 drivers/iio/industrialio-sw-device.o 5509 1528 64 7101 1bbd drivers/iio/industrialio-sw-trigger.o After: ===== text data bss dec hex filename 5133 1432 64 6629 19e5 drivers/iio/industrialio-sw-device.o 5605 1432 64 7101 1bbd drivers/iio/industrialio-sw-trigger.o Signed-off-by: Christophe JAILLET Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-sw-device.c | 2 +- drivers/iio/industrialio-sw-trigger.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/industrialio-sw-device.c b/drivers/iio/industrialio-sw-device.c index cdaf30a3f233..3582524de0b8 100644 --- a/drivers/iio/industrialio-sw-device.c +++ b/drivers/iio/industrialio-sw-device.c @@ -148,7 +148,7 @@ static void device_drop_group(struct config_group *group, config_item_put(item); } -static struct configfs_group_operations device_ops = { +static const struct configfs_group_operations device_ops = { .make_group = &device_make_group, .drop_item = &device_drop_group, }; diff --git a/drivers/iio/industrialio-sw-trigger.c b/drivers/iio/industrialio-sw-trigger.c index d86a3305d9e8..334b6b10a784 100644 --- a/drivers/iio/industrialio-sw-trigger.c +++ b/drivers/iio/industrialio-sw-trigger.c @@ -152,7 +152,7 @@ static void trigger_drop_group(struct config_group *group, config_item_put(item); } -static struct configfs_group_operations trigger_ops = { +static const struct configfs_group_operations trigger_ops = { .make_group = &trigger_make_group, .drop_item = &trigger_drop_group, }; From f9cc5b5a9e9a84d423cdc48882f1c93f1f90a32b Mon Sep 17 00:00:00 2001 From: Tao Zhang Date: Tue, 23 Dec 2025 18:09:50 +0800 Subject: [PATCH 115/297] coresight: tpda: add sysfs nodes for tpda cross-trigger configuration Introduce sysfs nodes to configure cross-trigger parameters for TPDA. These registers define the characteristics of cross-trigger packets, including generation frequency and flag values. Signed-off-by: Tao Zhang Reviewed-by: James Clark Co-developed-by: Jie Gan Signed-off-by: Jie Gan [ Fix kernel version in the Documentation ] Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251223-add_sysfs_nodes_to_configure_tpda-v8-1-4c95db608b62@oss.qualcomm.com --- .../testing/sysfs-bus-coresight-devices-tpda | 35 ++++++ drivers/hwtracing/coresight/coresight-tpda.c | 109 +++++++++++++++++- drivers/hwtracing/coresight/coresight-tpda.h | 63 +++++++++- 3 files changed, 200 insertions(+), 7 deletions(-) create mode 100644 Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda new file mode 100644 index 000000000000..8519a08444ab --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda @@ -0,0 +1,35 @@ +What: /sys/bus/coresight/devices//trig_async_enable +Date: December 2025 +KernelVersion: 6.20 +Contact: Jinlong Mao , Tao Zhang , Jie Gan +Description: + (RW) Enable/disable cross trigger synchronization sequence interface. + +What: /sys/bus/coresight/devices//trig_flag_ts_enable +Date: December 2025 +KernelVersion: 6.20 +Contact: Jinlong Mao , Tao Zhang , Jie Gan +Description: + (RW) Enable/disable cross trigger FLAG packet request interface. + +What: /sys/bus/coresight/devices//trig_freq_enable +Date: December 2025 +KernelVersion: 6.20 +Contact: Jinlong Mao , Tao Zhang , Jie Gan +Description: + (RW) Enable/disable cross trigger FREQ packet request interface. + +What: /sys/bus/coresight/devices//freq_ts_enable +Date: December 2025 +KernelVersion: 6.20 +Contact: Jinlong Mao , Tao Zhang , Jie Gan +Description: + (RW) Enable/disable the timestamp for all FREQ packets. + +What: /sys/bus/coresight/devices//cmbchan_mode +Date: December 2025 +KernelVersion: 6.20 +Contact: Jinlong Mao , Tao Zhang , Jie Gan +Description: + (RW) Configure the CMB/MCMB channel mode for all enabled ports. + Value 0 means raw channel mapping mode. Value 1 means channel pair marking mode. diff --git a/drivers/hwtracing/coresight/coresight-tpda.c b/drivers/hwtracing/coresight/coresight-tpda.c index 3a3825d27f86..2186223ad33e 100644 --- a/drivers/hwtracing/coresight/coresight-tpda.c +++ b/drivers/hwtracing/coresight/coresight-tpda.c @@ -137,12 +137,32 @@ static int tpda_get_element_size(struct tpda_drvdata *drvdata, /* Settings pre enabling port control register */ static void tpda_enable_pre_port(struct tpda_drvdata *drvdata) { - u32 val; + u32 val = 0; - val = readl_relaxed(drvdata->base + TPDA_CR); - val &= ~TPDA_CR_ATID; val |= FIELD_PREP(TPDA_CR_ATID, drvdata->atid); + if (drvdata->trig_async) + val |= TPDA_CR_SRIE; + + if (drvdata->trig_flag_ts) + val |= TPDA_CR_FLRIE; + + if (drvdata->trig_freq) + val |= TPDA_CR_FRIE; + + if (drvdata->freq_ts) + val |= TPDA_CR_FREQTS; + + if (drvdata->cmbchan_mode) + val |= TPDA_CR_CMBCHANMODE; + writel_relaxed(val, drvdata->base + TPDA_CR); + + /* + * If FLRIE bit is set, set the master and channel + * id as zero + */ + if (drvdata->trig_flag_ts) + writel_relaxed(0x0, drvdata->base + TPDA_FPID_CR); } static int tpda_enable_port(struct tpda_drvdata *drvdata, int port) @@ -258,6 +278,87 @@ static const struct coresight_ops tpda_cs_ops = { .link_ops = &tpda_link_ops, }; +/* Read cross-trigger register member */ +static ssize_t tpda_trig_sysfs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tpda_trig_sysfs_attribute *tpda_attr = + container_of(attr, struct tpda_trig_sysfs_attribute, attr); + struct tpda_drvdata *drvdata = dev_get_drvdata(dev->parent); + + guard(spinlock)(&drvdata->spinlock); + switch (tpda_attr->mem) { + case FREQTS: + return sysfs_emit(buf, "%u\n", (unsigned int)drvdata->freq_ts); + case FRIE: + return sysfs_emit(buf, "%u\n", (unsigned int)drvdata->trig_freq); + case FLRIE: + return sysfs_emit(buf, "%u\n", (unsigned int)drvdata->trig_flag_ts); + case SRIE: + return sysfs_emit(buf, "%u\n", (unsigned int)drvdata->trig_async); + case CMBCHANMODE: + return sysfs_emit(buf, "%u\n", (unsigned int)drvdata->cmbchan_mode); + + } + return -EINVAL; +} + +static ssize_t tpda_trig_sysfs_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + struct tpda_trig_sysfs_attribute *tpda_attr = + container_of(attr, struct tpda_trig_sysfs_attribute, attr); + struct tpda_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + guard(spinlock)(&drvdata->spinlock); + switch (tpda_attr->mem) { + case FREQTS: + drvdata->freq_ts = !!val; + break; + case FRIE: + drvdata->trig_freq = !!val; + break; + case FLRIE: + drvdata->trig_flag_ts = !!val; + break; + case SRIE: + drvdata->trig_async = !!val; + break; + case CMBCHANMODE: + drvdata->cmbchan_mode = !!val; + break; + default: + return -EINVAL; + } + + return size; +} + +static struct attribute *tpda_attrs[] = { + tpda_trig_sysfs_rw(freq_ts_enable, FREQTS), + tpda_trig_sysfs_rw(trig_freq_enable, FRIE), + tpda_trig_sysfs_rw(trig_flag_ts_enable, FLRIE), + tpda_trig_sysfs_rw(trig_async_enable, SRIE), + tpda_trig_sysfs_rw(cmbchan_mode, CMBCHANMODE), + NULL, +}; + +static struct attribute_group tpda_attr_grp = { + .attrs = tpda_attrs, +}; + +static const struct attribute_group *tpda_attr_grps[] = { + &tpda_attr_grp, + NULL, +}; + static int tpda_init_default_data(struct tpda_drvdata *drvdata) { int atid; @@ -273,6 +374,7 @@ static int tpda_init_default_data(struct tpda_drvdata *drvdata) return atid; drvdata->atid = atid; + drvdata->freq_ts = true; return 0; } @@ -316,6 +418,7 @@ static int tpda_probe(struct amba_device *adev, const struct amba_id *id) desc.ops = &tpda_cs_ops; desc.pdata = adev->dev.platform_data; desc.dev = &adev->dev; + desc.groups = tpda_attr_grps; desc.access = CSDEV_ACCESS_IOMEM(base); drvdata->csdev = coresight_register(&desc); if (IS_ERR(drvdata->csdev)) diff --git a/drivers/hwtracing/coresight/coresight-tpda.h b/drivers/hwtracing/coresight/coresight-tpda.h index c6af3d2da3ef..c93732e04af2 100644 --- a/drivers/hwtracing/coresight/coresight-tpda.h +++ b/drivers/hwtracing/coresight/coresight-tpda.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2023,2025 Qualcomm Innovation Center, Inc. All rights reserved. */ #ifndef _CORESIGHT_CORESIGHT_TPDA_H @@ -8,6 +8,25 @@ #define TPDA_CR (0x000) #define TPDA_Pn_CR(n) (0x004 + (n * 4)) +#define TPDA_FPID_CR (0x084) + +/* Cross trigger FREQ packets timestamp bit */ +#define TPDA_CR_FREQTS BIT(2) +/* Cross trigger FREQ packet request bit */ +#define TPDA_CR_FRIE BIT(3) +/* Cross trigger FLAG packet request interface bit */ +#define TPDA_CR_FLRIE BIT(4) +/* Cross trigger synchronization bit */ +#define TPDA_CR_SRIE BIT(5) +/* Bits 6 ~ 12 is for atid value */ +#define TPDA_CR_ATID GENMASK(12, 6) +/* + * Channel mode bit of the packetization of CMB/MCB traffic + * 0 - raw channel mapping mode + * 1 - channel pair marking mode + */ +#define TPDA_CR_CMBCHANMODE BIT(20) + /* Aggregator port enable bit */ #define TPDA_Pn_CR_ENA BIT(0) /* Aggregator port CMB data set element size bit */ @@ -17,9 +36,6 @@ #define TPDA_MAX_INPORTS 32 -/* Bits 6 ~ 12 is for atid value */ -#define TPDA_CR_ATID GENMASK(12, 6) - /** * struct tpda_drvdata - specifics associated to an TPDA component * @base: memory mapped base address for this component. @@ -29,6 +45,11 @@ * @enable: enable status of the component. * @dsb_esize Record the DSB element size. * @cmb_esize Record the CMB element size. + * @trig_async: Enable/disable cross trigger synchronization sequence interface. + * @trig_flag_ts: Enable/disable cross trigger FLAG packet request interface. + * @trig_freq: Enable/disable cross trigger FREQ packet request interface. + * @freq_ts: Enable/disable the timestamp for all FREQ packets. + * @cmbchan_mode: Configure the CMB/MCMB channel mode. */ struct tpda_drvdata { void __iomem *base; @@ -38,6 +59,40 @@ struct tpda_drvdata { u8 atid; u32 dsb_esize; u32 cmb_esize; + bool trig_async; + bool trig_flag_ts; + bool trig_freq; + bool freq_ts; + bool cmbchan_mode; }; +/* Enumerate members of global control register(cr) */ +enum tpda_cr_mem { + FREQTS, + FRIE, + FLRIE, + SRIE, + CMBCHANMODE +}; + +/** + * struct tpda_trig_sysfs_attribute - Record the member variables of cross + * trigger register that need to be operated by sysfs file + * @attr: The device attribute + * @mem: The member in the control register data structure + */ +struct tpda_trig_sysfs_attribute { + struct device_attribute attr; + enum tpda_cr_mem mem; +}; + +#define tpda_trig_sysfs_rw(name, mem) \ + (&((struct tpda_trig_sysfs_attribute[]) { \ + { \ + __ATTR(name, 0644, tpda_trig_sysfs_show, \ + tpda_trig_sysfs_store), \ + mem, \ + } \ + })[0].attr.attr) + #endif /* _CORESIGHT_CORESIGHT_TPDA_H */ From 8e1c358a3b0e69eb527bb6723366b92e982235c3 Mon Sep 17 00:00:00 2001 From: Jie Gan Date: Tue, 23 Dec 2025 18:09:51 +0800 Subject: [PATCH 116/297] coresight: tpda: add global_flush_req sysfs node Setting the global_flush_req register to 1 initiates a flush request for all enabled TPDA input ports. The register remains set until the flush operation is complete. Signed-off-by: Jie Gan [ Fix kernel version in the Documentation ] Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251223-add_sysfs_nodes_to_configure_tpda-v8-2-4c95db608b62@oss.qualcomm.com --- .../testing/sysfs-bus-coresight-devices-tpda | 8 ++++ drivers/hwtracing/coresight/coresight-tpda.c | 45 +++++++++++++++++++ drivers/hwtracing/coresight/coresight-tpda.h | 2 + 3 files changed, 55 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda index 8519a08444ab..acd354d7bdfa 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda @@ -33,3 +33,11 @@ Contact: Jinlong Mao , Tao Zhang /global_flush_req +Date: December 2025 +KernelVersion: 6.20 +Contact: Jinlong Mao , Tao Zhang , Jie Gan +Description: + (RW) Set global (all ports) flush request bit. The bit remains set until a + global flush request sequence completes. diff --git a/drivers/hwtracing/coresight/coresight-tpda.c b/drivers/hwtracing/coresight/coresight-tpda.c index 2186223ad33e..d24a9098f1b1 100644 --- a/drivers/hwtracing/coresight/coresight-tpda.c +++ b/drivers/hwtracing/coresight/coresight-tpda.c @@ -341,7 +341,52 @@ static ssize_t tpda_trig_sysfs_store(struct device *dev, return size; } +static ssize_t global_flush_req_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tpda_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val; + + if (!drvdata->csdev->refcnt) + return -EINVAL; + + guard(spinlock)(&drvdata->spinlock); + val = readl_relaxed(drvdata->base + TPDA_CR); + /* read global_flush_req bit */ + val &= TPDA_CR_FLREQ; + + return sysfs_emit(buf, "%lu\n", val); +} + +static ssize_t global_flush_req_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + struct tpda_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + if (!drvdata->csdev->refcnt || !val) + return -EINVAL; + + guard(spinlock)(&drvdata->spinlock); + val = readl_relaxed(drvdata->base + TPDA_CR); + /* set global_flush_req bit */ + val |= TPDA_CR_FLREQ; + CS_UNLOCK(drvdata->base); + writel_relaxed(val, drvdata->base + TPDA_CR); + CS_LOCK(drvdata->base); + + return size; +} +static DEVICE_ATTR_RW(global_flush_req); + static struct attribute *tpda_attrs[] = { + &dev_attr_global_flush_req.attr, tpda_trig_sysfs_rw(freq_ts_enable, FREQTS), tpda_trig_sysfs_rw(trig_freq_enable, FRIE), tpda_trig_sysfs_rw(trig_flag_ts_enable, FLRIE), diff --git a/drivers/hwtracing/coresight/coresight-tpda.h b/drivers/hwtracing/coresight/coresight-tpda.h index c93732e04af2..1cc9253293ec 100644 --- a/drivers/hwtracing/coresight/coresight-tpda.h +++ b/drivers/hwtracing/coresight/coresight-tpda.h @@ -10,6 +10,8 @@ #define TPDA_Pn_CR(n) (0x004 + (n * 4)) #define TPDA_FPID_CR (0x084) +/* Cross trigger global (all ports) flush request bit */ +#define TPDA_CR_FLREQ BIT(0) /* Cross trigger FREQ packets timestamp bit */ #define TPDA_CR_FREQTS BIT(2) /* Cross trigger FREQ packet request bit */ From 33f04ead7c498f29bca875783f13542e5ccd17ac Mon Sep 17 00:00:00 2001 From: Tao Zhang Date: Tue, 23 Dec 2025 18:09:52 +0800 Subject: [PATCH 117/297] coresight: tpda: add logic to configure TPDA_SYNCR register The TPDA_SYNC counter tracks the number of bytes transferred from the aggregator. When this count reaches the value programmed in the TPDA_SYNCR register, an ASYNC request is triggered, allowing userspace tools to accurately parse each valid packet. Signed-off-by: Tao Zhang Reviewed-by: James Clark Co-developed-by: Jie Gan Signed-off-by: Jie Gan [ Fix kernel version in Documentation ] Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251223-add_sysfs_nodes_to_configure_tpda-v8-3-4c95db608b62@oss.qualcomm.com --- .../testing/sysfs-bus-coresight-devices-tpda | 18 ++++ drivers/hwtracing/coresight/coresight-tpda.c | 90 +++++++++++++++++++ drivers/hwtracing/coresight/coresight-tpda.h | 10 +++ 3 files changed, 118 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda index acd354d7bdfa..bd86e6fd961d 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda @@ -41,3 +41,21 @@ Contact: Jinlong Mao , Tao Zhang /syncr_mode +Date: December 2025 +KernelVersion: 6.20 +Contact: Jinlong Mao , Tao Zhang , Jie Gan +Description: + (RW) Set mode the of the syncr counter. + mode 0 - COUNT[11:0] value represents the approximate number of bytes moved between two ASYNC packet requests + mode 1 - the bits COUNT[11:7] are used as a power of 2. for example, we could insert an async packet every 8K + data by writing a value 13 to the COUNT[11:7] field. + +What: /sys/bus/coresight/devices//syncr_count +Date: December 2025 +KernelVersion: 6.20 +Contact: Jinlong Mao , Tao Zhang , Jie Gan +Description: + (RW) Set value the of the syncr counter. + Range: 0-4095 diff --git a/drivers/hwtracing/coresight/coresight-tpda.c b/drivers/hwtracing/coresight/coresight-tpda.c index d24a9098f1b1..deb5a646a04c 100644 --- a/drivers/hwtracing/coresight/coresight-tpda.c +++ b/drivers/hwtracing/coresight/coresight-tpda.c @@ -163,6 +163,20 @@ static void tpda_enable_pre_port(struct tpda_drvdata *drvdata) */ if (drvdata->trig_flag_ts) writel_relaxed(0x0, drvdata->base + TPDA_FPID_CR); + + /* Initialize with a value of 0 */ + val = 0; + if (drvdata->syncr_mode) + val |= TPDA_SYNCR_MODE_CTRL_MASK; + + if (drvdata->syncr_count > 0 && + drvdata->syncr_count < TPDA_SYNCR_COUNT_MASK) + val |= drvdata->syncr_count; + else + /* Program the count to its MAX value by default */ + val |= TPDA_SYNCR_COUNT_MASK; + + writel_relaxed(val, drvdata->base + TPDA_SYNCR); } static int tpda_enable_port(struct tpda_drvdata *drvdata, int port) @@ -385,8 +399,84 @@ static ssize_t global_flush_req_store(struct device *dev, } static DEVICE_ATTR_RW(global_flush_req); +static ssize_t syncr_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tpda_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val, syncr_val; + + if (!drvdata->csdev->refcnt) + return -EINVAL; + + guard(spinlock)(&drvdata->spinlock); + syncr_val = readl_relaxed(drvdata->base + TPDA_SYNCR); + val = FIELD_GET(TPDA_SYNCR_MODE_CTRL_MASK, syncr_val); + + return sysfs_emit(buf, "%lu\n", val); +} + +static ssize_t syncr_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + struct tpda_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + guard(spinlock)(&drvdata->spinlock); + /* set the mode when first enabling the device */ + drvdata->syncr_mode = !!val; + + return size; +} +static DEVICE_ATTR_RW(syncr_mode); + +static ssize_t syncr_count_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tpda_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val; + + if (!drvdata->csdev->refcnt) + return -EINVAL; + + guard(spinlock)(&drvdata->spinlock); + val = readl_relaxed(drvdata->base + TPDA_SYNCR); + val &= TPDA_SYNCR_COUNT_MASK; + + return sysfs_emit(buf, "%lu\n", val); +} + +static ssize_t syncr_count_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + struct tpda_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + if (val > TPDA_SYNCR_COUNT_MASK) + return -EINVAL; + + guard(spinlock)(&drvdata->spinlock); + drvdata->syncr_count = val; + + return size; +} +static DEVICE_ATTR_RW(syncr_count); + static struct attribute *tpda_attrs[] = { &dev_attr_global_flush_req.attr, + &dev_attr_syncr_mode.attr, + &dev_attr_syncr_count.attr, tpda_trig_sysfs_rw(freq_ts_enable, FREQTS), tpda_trig_sysfs_rw(trig_freq_enable, FRIE), tpda_trig_sysfs_rw(trig_flag_ts_enable, FLRIE), diff --git a/drivers/hwtracing/coresight/coresight-tpda.h b/drivers/hwtracing/coresight/coresight-tpda.h index 1cc9253293ec..56d35697303a 100644 --- a/drivers/hwtracing/coresight/coresight-tpda.h +++ b/drivers/hwtracing/coresight/coresight-tpda.h @@ -9,6 +9,7 @@ #define TPDA_CR (0x000) #define TPDA_Pn_CR(n) (0x004 + (n * 4)) #define TPDA_FPID_CR (0x084) +#define TPDA_SYNCR (0x08C) /* Cross trigger global (all ports) flush request bit */ #define TPDA_CR_FLREQ BIT(0) @@ -36,6 +37,11 @@ /* Aggregator port DSB data set element size bit */ #define TPDA_Pn_CR_DSBSIZE BIT(8) +/* TPDA_SYNCR count mask */ +#define TPDA_SYNCR_COUNT_MASK GENMASK(11, 0) +/* TPDA_SYNCR mode control bit */ +#define TPDA_SYNCR_MODE_CTRL_MASK GENMASK(12, 12) + #define TPDA_MAX_INPORTS 32 /** @@ -52,6 +58,8 @@ * @trig_freq: Enable/disable cross trigger FREQ packet request interface. * @freq_ts: Enable/disable the timestamp for all FREQ packets. * @cmbchan_mode: Configure the CMB/MCMB channel mode. + * @syncr_mode: Setting the mode for counting packets. + * @syncr_count: Setting the value of the count. */ struct tpda_drvdata { void __iomem *base; @@ -66,6 +74,8 @@ struct tpda_drvdata { bool trig_freq; bool freq_ts; bool cmbchan_mode; + bool syncr_mode; + u32 syncr_count; }; /* Enumerate members of global control register(cr) */ From a089d585a7f4fa85b5e1d78c5308e27a0d875b17 Mon Sep 17 00:00:00 2001 From: Tao Zhang Date: Tue, 23 Dec 2025 18:09:53 +0800 Subject: [PATCH 118/297] coresight: tpda: add sysfs node to flush specific port Setting bit i in the TPDA_FLUSH_CR register initiates a flush request for port i, forcing the data to synchronize and be transmitted to the sink device. Signed-off-by: Tao Zhang Reviewed-by: James Clark Co-developed-by: Jie Gan Signed-off-by: Jie Gan [ Fix kernel version in Documentation ] Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251223-add_sysfs_nodes_to_configure_tpda-v8-4-4c95db608b62@oss.qualcomm.com --- .../testing/sysfs-bus-coresight-devices-tpda | 8 ++++ drivers/hwtracing/coresight/coresight-tpda.c | 40 +++++++++++++++++++ drivers/hwtracing/coresight/coresight-tpda.h | 1 + 3 files changed, 49 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda index bd86e6fd961d..54f05964a360 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda @@ -59,3 +59,11 @@ Contact: Jinlong Mao , Tao Zhang /port_flush_req +Date: December 2025 +KernelVersion: 6.20 +Contact: Jinlong Mao , Tao Zhang , Jie Gan +Description: + (RW) Configure the bit i to requests a flush operation of port i on the TPDA. + The requested bit(s) remain set until the flush request completes. diff --git a/drivers/hwtracing/coresight/coresight-tpda.c b/drivers/hwtracing/coresight/coresight-tpda.c index deb5a646a04c..7055f8f13427 100644 --- a/drivers/hwtracing/coresight/coresight-tpda.c +++ b/drivers/hwtracing/coresight/coresight-tpda.c @@ -473,10 +473,50 @@ static ssize_t syncr_count_store(struct device *dev, } static DEVICE_ATTR_RW(syncr_count); +static ssize_t port_flush_req_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tpda_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val; + + if (!drvdata->csdev->refcnt) + return -EINVAL; + + guard(spinlock)(&drvdata->spinlock); + val = readl_relaxed(drvdata->base + TPDA_FLUSH_CR); + + return sysfs_emit(buf, "0x%lx\n", val); +} + +static ssize_t port_flush_req_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + struct tpda_drvdata *drvdata = dev_get_drvdata(dev->parent); + u32 val; + + if (kstrtou32(buf, 0, &val)) + return -EINVAL; + + if (!drvdata->csdev->refcnt || !val) + return -EINVAL; + + guard(spinlock)(&drvdata->spinlock); + CS_UNLOCK(drvdata->base); + writel_relaxed(val, drvdata->base + TPDA_FLUSH_CR); + CS_LOCK(drvdata->base); + + return size; +} +static DEVICE_ATTR_RW(port_flush_req); + static struct attribute *tpda_attrs[] = { &dev_attr_global_flush_req.attr, &dev_attr_syncr_mode.attr, &dev_attr_syncr_count.attr, + &dev_attr_port_flush_req.attr, tpda_trig_sysfs_rw(freq_ts_enable, FREQTS), tpda_trig_sysfs_rw(trig_freq_enable, FRIE), tpda_trig_sysfs_rw(trig_flag_ts_enable, FLRIE), diff --git a/drivers/hwtracing/coresight/coresight-tpda.h b/drivers/hwtracing/coresight/coresight-tpda.h index 56d35697303a..a090352009bb 100644 --- a/drivers/hwtracing/coresight/coresight-tpda.h +++ b/drivers/hwtracing/coresight/coresight-tpda.h @@ -10,6 +10,7 @@ #define TPDA_Pn_CR(n) (0x004 + (n * 4)) #define TPDA_FPID_CR (0x084) #define TPDA_SYNCR (0x08C) +#define TPDA_FLUSH_CR (0x090) /* Cross trigger global (all ports) flush request bit */ #define TPDA_CR_FLREQ BIT(0) From 98baf887b1e9ae69178b25ed49cda1a6f01905a3 Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 7 Jan 2026 11:26:23 +0000 Subject: [PATCH 119/297] coresight: tpda: Fix intendation for sysfs interface documentation linux-next merge complains about build break with make htmldocs : Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda:45: ERROR: Unexpected indentation. [docutils] Closes: https://lkml.kernel.org/r/20260106114933.638b073f@canb.auug.org.au Reported-by: Stephen Rothwell Cc: Stephen Rothwell Cc: Jie Gan Cc: Tao Zhang Signed-off-by: Suzuki K Poulose --- Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda index 54f05964a360..650431feae45 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda @@ -50,7 +50,7 @@ Description: (RW) Set mode the of the syncr counter. mode 0 - COUNT[11:0] value represents the approximate number of bytes moved between two ASYNC packet requests mode 1 - the bits COUNT[11:7] are used as a power of 2. for example, we could insert an async packet every 8K - data by writing a value 13 to the COUNT[11:7] field. + data by writing a value 13 to the COUNT[11:7] field. What: /sys/bus/coresight/devices//syncr_count Date: December 2025 From b92489be8048b236e1f44e3b1c7100f4296859e7 Mon Sep 17 00:00:00 2001 From: Jose Javier Rodriguez Barbarin Date: Thu, 8 Jan 2026 14:53:01 +0100 Subject: [PATCH 120/297] iio: adc: men_z188_adc: drop unneeded MODULE_ALIAS Since commit 1f4ea4838b13 ("mcb: Add missing modpost build support") the MODULE_ALIAS() is redundant as the module alias is now automatically generated from the MODULE_DEVICE_TABLE(). Remove the explicit alias. No functional change intended. Reviewed-by: Jorge Sanjuan Garcia Signed-off-by: Jose Javier Rodriguez Barbarin Signed-off-by: Jonathan Cameron --- drivers/iio/adc/men_z188_adc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/iio/adc/men_z188_adc.c b/drivers/iio/adc/men_z188_adc.c index cf8a8c0412ec..90919d282e7b 100644 --- a/drivers/iio/adc/men_z188_adc.c +++ b/drivers/iio/adc/men_z188_adc.c @@ -171,5 +171,4 @@ module_mcb_driver(men_z188_driver); MODULE_AUTHOR("Johannes Thumshirn "); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("IIO ADC driver for MEN 16z188 ADC Core"); -MODULE_ALIAS("mcb:16z188"); MODULE_IMPORT_NS("MCB"); From e4d0e63e2442de43421a5e8ace8eb36b997345b9 Mon Sep 17 00:00:00 2001 From: Shrikant Raskar Date: Thu, 1 Jan 2026 21:47:38 +0530 Subject: [PATCH 121/297] dt-bindings: iio: proximity: Add RF Digital RFD77402 ToF sensor The RF Digital RFD77402 is a Time-of-Flight (ToF) proximity and distance sensor that provides absolute and highly accurate distance measurements from 100 mm up to 2000 mm over an I2C interface. It includes an optional interrupt pin that signals when new measurement data is ready. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Shrikant Raskar Signed-off-by: Jonathan Cameron --- .../iio/proximity/rfdigital,rfd77402.yaml | 53 +++++++++++++++++++ .../devicetree/bindings/vendor-prefixes.yaml | 2 + 2 files changed, 55 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/proximity/rfdigital,rfd77402.yaml diff --git a/Documentation/devicetree/bindings/iio/proximity/rfdigital,rfd77402.yaml b/Documentation/devicetree/bindings/iio/proximity/rfdigital,rfd77402.yaml new file mode 100644 index 000000000000..1ef6326b209e --- /dev/null +++ b/Documentation/devicetree/bindings/iio/proximity/rfdigital,rfd77402.yaml @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/proximity/rfdigital,rfd77402.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: RF Digital RFD77402 ToF sensor + +maintainers: + - Shrikant Raskar + +description: + The RF Digital RFD77402 is a Time-of-Flight (ToF) proximity and distance + sensor providing up to 200 mm range measurement over an I2C interface. + +properties: + compatible: + const: rfdigital,rfd77402 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + description: + Interrupt asserted when a new distance measurement is available. + + vdd-supply: + description: Regulator that provides power to the sensor. + +required: + - compatible + - reg + - vdd-supply + +additionalProperties: false + +examples: + - | + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + proximity@4c { + compatible = "rfdigital,rfd77402"; + reg = <0x4c>; + vdd-supply = <&vdd_3v3>; + interrupt-parent = <&gpio>; + interrupts = <4 IRQ_TYPE_EDGE_FALLING>; + }; + }; +... diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index c7591b2aec2a..59ac4f0756d9 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1361,6 +1361,8 @@ patternProperties: description: Revolution Robotics, Inc. (Revotics) "^rex,.*": description: iMX6 Rex Project + "^rfdigital,.*": + description: RF Digital Corporation "^richtek,.*": description: Richtek Technology Corporation "^ricoh,.*": From a750088883da1d21502d26bf5852a0d49efb1604 Mon Sep 17 00:00:00 2001 From: Shrikant Raskar Date: Thu, 1 Jan 2026 21:47:39 +0530 Subject: [PATCH 122/297] iio: proximity: rfd77402: Add OF device ID for enumeration via DT Add an OF device ID table so the driver can bind automatically when the RFD77402 sensor is described in Device Tree. This enables proper enumeration via its compatible string and allows instantiation on DT-based platforms. Signed-off-by: Shrikant Raskar Signed-off-by: Jonathan Cameron --- drivers/iio/proximity/rfd77402.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/iio/proximity/rfd77402.c b/drivers/iio/proximity/rfd77402.c index aff60a3c1a6f..3262af6f6882 100644 --- a/drivers/iio/proximity/rfd77402.c +++ b/drivers/iio/proximity/rfd77402.c @@ -313,10 +313,17 @@ static const struct i2c_device_id rfd77402_id[] = { }; MODULE_DEVICE_TABLE(i2c, rfd77402_id); +static const struct of_device_id rfd77402_of_match[] = { + { .compatible = "rfdigital,rfd77402" }, + { } +}; +MODULE_DEVICE_TABLE(of, rfd77402_of_match); + static struct i2c_driver rfd77402_driver = { .driver = { .name = RFD77402_DRV_NAME, .pm = pm_sleep_ptr(&rfd77402_pm_ops), + .of_match_table = rfd77402_of_match, }, .probe = rfd77402_probe, .id_table = rfd77402_id, From c84cde33b00451c7380482d19feef67d04deae7d Mon Sep 17 00:00:00 2001 From: Michael Harris Date: Tue, 6 Jan 2026 06:50:55 -0800 Subject: [PATCH 123/297] staging: iio: adt7316: modernize power management Replaced use of deprecated function SIMPLE_DEV_PM_OPS() with EXPORT_GPL_SIMPLE_DEV_PM_OPS(). Removed PM preprocessor conditions with usage of pm_sleep_ptr(). Signed-off-by: Michael Harris Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/staging/iio/addac/adt7316-i2c.c | 2 +- drivers/staging/iio/addac/adt7316-spi.c | 2 +- drivers/staging/iio/addac/adt7316.c | 6 ++---- drivers/staging/iio/addac/adt7316.h | 6 +----- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/drivers/staging/iio/addac/adt7316-i2c.c b/drivers/staging/iio/addac/adt7316-i2c.c index f45968ef94ea..3bdaee925dee 100644 --- a/drivers/staging/iio/addac/adt7316-i2c.c +++ b/drivers/staging/iio/addac/adt7316-i2c.c @@ -136,7 +136,7 @@ static struct i2c_driver adt7316_driver = { .driver = { .name = "adt7316", .of_match_table = adt7316_of_match, - .pm = ADT7316_PM_OPS, + .pm = pm_sleep_ptr(&adt7316_pm_ops), }, .probe = adt7316_i2c_probe, .id_table = adt7316_i2c_id, diff --git a/drivers/staging/iio/addac/adt7316-spi.c b/drivers/staging/iio/addac/adt7316-spi.c index af513e003da7..f91325d11394 100644 --- a/drivers/staging/iio/addac/adt7316-spi.c +++ b/drivers/staging/iio/addac/adt7316-spi.c @@ -142,7 +142,7 @@ static struct spi_driver adt7316_driver = { .driver = { .name = "adt7316", .of_match_table = adt7316_of_spi_match, - .pm = ADT7316_PM_OPS, + .pm = pm_sleep_ptr(&adt7316_pm_ops), }, .probe = adt7316_spi_probe, .id_table = adt7316_spi_id, diff --git a/drivers/staging/iio/addac/adt7316.c b/drivers/staging/iio/addac/adt7316.c index 8a9a8262c2be..59fb3bd26bc1 100644 --- a/drivers/staging/iio/addac/adt7316.c +++ b/drivers/staging/iio/addac/adt7316.c @@ -2082,7 +2082,6 @@ static const struct attribute_group adt7516_event_attribute_group = { .name = "events", }; -#ifdef CONFIG_PM_SLEEP static int adt7316_disable(struct device *dev) { struct iio_dev *dev_info = dev_get_drvdata(dev); @@ -2098,9 +2097,8 @@ static int adt7316_enable(struct device *dev) return _adt7316_store_enabled(chip, 1); } -EXPORT_SYMBOL_GPL(adt7316_pm_ops); -SIMPLE_DEV_PM_OPS(adt7316_pm_ops, adt7316_disable, adt7316_enable); -#endif + +EXPORT_GPL_SIMPLE_DEV_PM_OPS(adt7316_pm_ops, adt7316_disable, adt7316_enable); static const struct iio_info adt7316_info = { .attrs = &adt7316_attribute_group, diff --git a/drivers/staging/iio/addac/adt7316.h b/drivers/staging/iio/addac/adt7316.h index 8c2a92ae7157..f208f0d3583a 100644 --- a/drivers/staging/iio/addac/adt7316.h +++ b/drivers/staging/iio/addac/adt7316.h @@ -22,12 +22,8 @@ struct adt7316_bus { int (*multi_write)(void *client, u8 first_reg, u8 count, u8 *data); }; -#ifdef CONFIG_PM_SLEEP extern const struct dev_pm_ops adt7316_pm_ops; -#define ADT7316_PM_OPS (&adt7316_pm_ops) -#else -#define ADT7316_PM_OPS NULL -#endif + int adt7316_probe(struct device *dev, struct adt7316_bus *bus, const char *name); From f69b5ac682dbc61e6aca806c22ce2ae74d598e45 Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Tue, 6 Jan 2026 05:45:19 +0000 Subject: [PATCH 124/297] iio: bmi270_i2c: Add MODULE_DEVICE_TABLE for BMI260/270 Currently BMI260 & BMI270 devices do not automatically load this driver. To fix this, add missing MODULE_DEVICE_TABLE for the i2c, acpi, and of device tables so the driver will load when the hardware is detected. Tested on my OneXPlayer F1 Pro. Signed-off-by: Derek J. Clark Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/imu/bmi270/bmi270_i2c.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/iio/imu/bmi270/bmi270_i2c.c b/drivers/iio/imu/bmi270/bmi270_i2c.c index b909a421ad01..b92da4e0776f 100644 --- a/drivers/iio/imu/bmi270/bmi270_i2c.c +++ b/drivers/iio/imu/bmi270/bmi270_i2c.c @@ -37,6 +37,7 @@ static const struct i2c_device_id bmi270_i2c_id[] = { { "bmi270", (kernel_ulong_t)&bmi270_chip_info }, { } }; +MODULE_DEVICE_TABLE(i2c, bmi270_i2c_id); static const struct acpi_device_id bmi270_acpi_match[] = { /* GPD Win Mini, Aya Neo AIR Pro, OXP Mini Pro, etc. */ @@ -45,12 +46,14 @@ static const struct acpi_device_id bmi270_acpi_match[] = { { "BMI0260", (kernel_ulong_t)&bmi260_chip_info }, { } }; +MODULE_DEVICE_TABLE(acpi, bmi270_acpi_match); static const struct of_device_id bmi270_of_match[] = { { .compatible = "bosch,bmi260", .data = &bmi260_chip_info }, { .compatible = "bosch,bmi270", .data = &bmi270_chip_info }, { } }; +MODULE_DEVICE_TABLE(of, bmi270_of_match); static struct i2c_driver bmi270_i2c_driver = { .driver = { From b96261d7be11f5a92998f5a5952d625d9f3ca6d6 Mon Sep 17 00:00:00 2001 From: Kurt Borja Date: Tue, 6 Jan 2026 03:17:32 -0500 Subject: [PATCH 125/297] iio: adc: ti-ads1018: Drop stale kernel-doc function context The driver no longer uses iio_device_claim_buffer_mode(). Drop it from ads1018_spi_read_exclusive() context remark. Signed-off-by: Kurt Borja Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ti-ads1018.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/iio/adc/ti-ads1018.c b/drivers/iio/adc/ti-ads1018.c index 286e06dc70b8..6246b3cab71f 100644 --- a/drivers/iio/adc/ti-ads1018.c +++ b/drivers/iio/adc/ti-ads1018.c @@ -211,8 +211,7 @@ static u32 ads1018_calc_delay(unsigned int hz) * Reads the most recent ADC conversion value, without updating the * device's configuration. * - * Context: Expects iio_device_claim_buffer_mode() is held and SPI bus - * *exclusive* use. + * Context: Expects SPI bus *exclusive* use. * * Return: 0 on success, negative errno on error. */ From b8d1936d052cf932b5cb4e44e79e864cbd9e9941 Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Wed, 7 Jan 2026 16:29:20 +0800 Subject: [PATCH 126/297] iio: adc: ad7476: Remove duplicate include Remove duplicate inclusion of linux/bitops.h. Signed-off-by: Chen Ni Reviewed-by: Matti Vaittinen Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7476.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/iio/adc/ad7476.c b/drivers/iio/adc/ad7476.c index 1bec6657394c..21d3f6aae972 100644 --- a/drivers/iio/adc/ad7476.c +++ b/drivers/iio/adc/ad7476.c @@ -16,7 +16,6 @@ #include #include #include -#include #include #include From 8b59bcf8d5cacbd0688ccdb87616704c04ae6ee3 Mon Sep 17 00:00:00 2001 From: Marcelo Schmitt Date: Wed, 7 Jan 2026 11:47:37 -0300 Subject: [PATCH 127/297] dt-bindings: iio: adc: Add AD4134 Add device tree documentation for AD4134 24-Bit, 4-channel simultaneous sampling, precision ADC. Reviewed-by: Conor Dooley Signed-off-by: Marcelo Schmitt Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/adi,ad4134.yaml | 191 ++++++++++++++++++ MAINTAINERS | 7 + 2 files changed, 198 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ad4134.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4134.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4134.yaml new file mode 100644 index 000000000000..ea6d7e026419 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4134.yaml @@ -0,0 +1,191 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/adi,ad4134.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AD4134 ADC + +maintainers: + - Marcelo Schmitt + +description: | + The AD4134 is a quad channel, low noise, simultaneous sampling, precision + analog-to-digital converter (ADC). + Specifications can be found at: + https://www.analog.com/media/en/technical-documentation/data-sheets/ad4134.pdf + +$ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + enum: + - adi,ad4134 + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 50000000 + + avdd5-supply: + description: A 5V supply that powers the chip's analog circuitry. + + dvdd5-supply: + description: A 5V supply that powers the chip's digital circuitry. + + iovdd-supply: + description: + A 1.8V supply that sets the logic levels for the digital interface pins. + + refin-supply: + description: + A 4.096V or 5V supply that serves as reference for ADC conversions. + + avdd1v8-supply: + description: A 1.8V supply used by the analog circuitry. + + dvdd1v8-supply: + description: A 1.8V supply used by the digital circuitry. + + clkvdd-supply: + description: A 1.8V supply for the chip's clock management circuit. + + ldoin-supply: + description: + A 2.6V to 5.5V supply that generates 1.8V for AVDD1V8, DVDD1V8, and CLKVDD + pins. + + clocks: + maxItems: 1 + description: + Required external clock source. Can specify either a crystal or CMOS clock + source. If an external crystal is set, connect the CLKSEL pin to IOVDD. + Otherwise, connect the CLKSEL pin to IOGND and the external CMOS clock + signal to the XTAL2/CLKIN pin. + + clock-names: + enum: + - xtal + - clkin + default: clkin + + '#clock-cells': + const: 0 + + clock-output-names: + maxItems: 1 + + regulators: + type: object + description: + list of regulators provided by this controller. + + properties: + vcm-output: + $ref: /schemas/regulator/regulator.yaml# + type: object + unevaluatedProperties: false + + additionalProperties: false + + reset-gpios: + maxItems: 1 + + powerdown-gpios: + description: + Active low GPIO connected to the /PDN pin. Forces the device into full + power-down mode when brought low. Pull this input to IOVDD for normal + operation. + maxItems: 1 + + odr-gpios: + description: + GPIO connected to ODR pin. Used to sample ADC data in minimum I/O mode. + maxItems: 1 + + adi,asrc-mode: + $ref: /schemas/types.yaml#/definitions/string + description: + Asynchronous Sample Rate Converter (ASRC) operation mode control input. + Describes whether the MODE pin is set to a high level (for master mode + operation) or to a low level (for slave mode operation). + enum: [ high, low ] + default: low + + adi,dclkio: + description: + DCLK pin I/O direction control for when the device operates in Pin Control + Slave Mode or in SPI Control Mode. Describes if DEC0/DCLKIO pin is at a + high level (which configures DCLK as an output) or to set to a low level + (configuring DCLK for input). + enum: [ out, in ] + default: in + + adi,dclkmode: + description: + DCLK mode control for when the device operates in Pin Control Slave Mode + or in SPI Control Mode. Describes whether the DEC1/DCLKMODE pin is set to + a high level (configuring the DCLK to operate in free running mode) or + to a low level (to configure DCLK to operate in gated mode). + enum: [ free-running, gated ] + default: gated + +required: + - compatible + - reg + - avdd5-supply + - dvdd5-supply + - iovdd-supply + - refin-supply + - clocks + - clock-names + +oneOf: + - required: + - ldoin-supply + - required: + - avdd1v8-supply + - dvdd1v8-supply + - clkvdd-supply + +unevaluatedProperties: false + +examples: + - | + #include + + spi { + #address-cells = <1>; + #size-cells = <0>; + + adc@0 { + compatible = "adi,ad4134"; + reg = <0>; + + spi-max-frequency = <1000000>; + + reset-gpios = <&gpio0 86 GPIO_ACTIVE_LOW>; + odr-gpios = <&gpio0 87 GPIO_ACTIVE_HIGH>; + powerdown-gpios = <&gpio0 88 GPIO_ACTIVE_LOW>; + + clocks = <&sys_clk>; + clock-names = "clkin"; + + avdd5-supply = <&avdd5>; + dvdd5-supply = <&dvdd5>; + iovdd-supply = <&iovdd>; + refin-supply = <&refin>; + avdd1v8-supply = <&avdd1v8>; + dvdd1v8-supply = <&dvdd1v8>; + clkvdd-supply = <&clkvdd>; + + regulators { + vcm_reg: vcm-output { + regulator-name = "ad4134-vcm"; + }; + }; + + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 2e8825b8ccef..3f9f6dd96ae4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1458,6 +1458,13 @@ F: Documentation/ABI/testing/sysfs-bus-iio-adc-ad4130 F: Documentation/devicetree/bindings/iio/adc/adi,ad4130.yaml F: drivers/iio/adc/ad4130.c +ANALOG DEVICES INC AD4134 DRIVER +M: Marcelo Schmitt +L: linux-iio@vger.kernel.org +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/adc/adi,ad4134.yaml + ANALOG DEVICES INC AD4170-4 DRIVER M: Marcelo Schmitt L: linux-iio@vger.kernel.org From e0bc6d7e258486c10bb11e31fd4421c134063b1d Mon Sep 17 00:00:00 2001 From: Marcelo Schmitt Date: Wed, 7 Jan 2026 11:47:59 -0300 Subject: [PATCH 128/297] iio: adc: Initial support for AD4134 AD4134 is a 24-bit, 4-channel, simultaneous sampling, precision analog-to-digital converter (ADC). The device can be managed through SPI or direct control of pin logical levels (pin control mode). The AD4134 design also features a dedicated bus for ADC sample data output. Though, this initial driver for AD4134 only supports usual SPI connections. Add basic support for AD4134 that enables single-shot ADC sample read. Signed-off-by: Marcelo Schmitt Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 11 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ad4134.c | 500 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 513 insertions(+) create mode 100644 drivers/iio/adc/ad4134.c diff --git a/MAINTAINERS b/MAINTAINERS index 3f9f6dd96ae4..4dd9f758a871 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1464,6 +1464,7 @@ L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/adc/adi,ad4134.yaml +F: drivers/iio/adc/ad4134.c ANALOG DEVICES INC AD4170-4 DRIVER M: Marcelo Schmitt diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 89a6486135f6..b4295aa415bf 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -112,6 +112,17 @@ config AD4130 To compile this driver as a module, choose M here: the module will be called ad4130. +config AD4134 + tristate "Analog Device AD4134 ADC Driver" + depends on SPI + select REGMAP_SPI + select CRC8 + help + Say yes here to build support for Analog Devices AD4134 SPI analog to + digital converters (ADC). + + To compile this driver as a module, choose M here: the module will be + called ad4134_spi. config AD4170_4 tristate "Analog Device AD4170-4 ADC Driver" diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 0a199630c081..c76550415ff1 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_AD4030) += ad4030.o obj-$(CONFIG_AD4062) += ad4062.o obj-$(CONFIG_AD4080) += ad4080.o obj-$(CONFIG_AD4130) += ad4130.o +obj-$(CONFIG_AD4134) += ad4134.o obj-$(CONFIG_AD4170_4) += ad4170-4.o obj-$(CONFIG_AD4695) += ad4695.o obj-$(CONFIG_AD4851) += ad4851.o diff --git a/drivers/iio/adc/ad4134.c b/drivers/iio/adc/ad4134.c new file mode 100644 index 000000000000..e42ee328fcbf --- /dev/null +++ b/drivers/iio/adc/ad4134.c @@ -0,0 +1,500 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2026 Analog Devices, Inc. + * Author: Marcelo Schmitt + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AD4134_RESET_TIME_US (10 * USEC_PER_SEC) + +#define AD4134_REG_READ_MASK BIT(7) +#define AD4134_SPI_MAX_XFER_LEN 3 + +#define AD4134_EXT_CLOCK_MHZ (48 * HZ_PER_MHZ) + +#define AD4134_NUM_CHANNELS 4 +#define AD4134_CHAN_PRECISION_BITS 24 + +#define AD4134_IFACE_CONFIG_A_REG 0x00 +#define AD4134_IFACE_CONFIG_B_REG 0x01 +#define AD4134_IFACE_CONFIG_B_SINGLE_INSTR BIT(7) + +#define AD4134_DEVICE_CONFIG_REG 0x02 +#define AD4134_DEVICE_CONFIG_POWER_MODE_MASK BIT(0) +#define AD4134_POWER_MODE_HIGH_PERF 0x1 + +#define AD4134_SILICON_REV_REG 0x07 +#define AD4134_SCRATCH_PAD_REG 0x0A +#define AD4134_STREAM_MODE_REG 0x0E +#define AD4134_SDO_PIN_SRC_SEL_REG 0x10 +#define AD4134_SDO_PIN_SRC_SEL_SDO_SEL_MASK BIT(2) + +#define AD4134_DATA_PACKET_CONFIG_REG 0x11 +#define AD4134_DATA_PACKET_CONFIG_FRAME_MASK GENMASK(5, 4) +#define AD4134_DATA_PACKET_24BIT_FRAME 0x2 + +#define AD4134_DIG_IF_CFG_REG 0x12 +#define AD4134_DIF_IF_CFG_FORMAT_MASK GENMASK(1, 0) +#define AD4134_DATA_FORMAT_SINGLE_CH_MODE 0x0 + +#define AD4134_PW_DOWN_CTRL_REG 0x13 +#define AD4134_DEVICE_STATUS_REG 0x15 +#define AD4134_ODR_VAL_INT_LSB_REG 0x16 +#define AD4134_CH3_OFFSET_MSB_REG 0x3E +#define AD4134_AIN_OR_ERROR_REG 0x48 + +/* + * AD4134 register map ends at address 0x48 and there is no register for + * retrieving ADC sample data. Though, to make use of Linux regmap API both + * for register access and sample read, we define one virtual register for each + * ADC channel. AD4134_CH_VREG(x) maps a channel number to it's virtual register + * address while AD4134_VREG_CH(x) tells which channel given the address. + */ +#define AD4134_CH_VREG(x) ((x) + 0x50) +#define AD4134_VREG_CH(x) ((x) - 0x50) + +#define AD4134_SPI_CRC_POLYNOM 0x07 +#define AD4134_SPI_CRC_INIT_VALUE 0xA5 +static unsigned char ad4134_spi_crc_table[CRC8_TABLE_SIZE]; + +#define AD4134_CHANNEL(_index) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec ad4134_chan_set[] = { + AD4134_CHANNEL(0), + AD4134_CHANNEL(1), + AD4134_CHANNEL(2), + AD4134_CHANNEL(3), +}; + +struct ad4134_state { + struct spi_device *spi; + struct regmap *regmap; + unsigned long sys_clk_hz; + struct gpio_desc *odr_gpio; + int refin_mv; + /* + * DMA (thus cache coherency maintenance) requires the transfer buffers + * to live in their own cache lines. + */ + u8 rx_buf[AD4134_SPI_MAX_XFER_LEN] __aligned(IIO_DMA_MINALIGN); + u8 tx_buf[AD4134_SPI_MAX_XFER_LEN]; +}; + +static const struct regmap_range ad4134_regmap_rd_range[] = { + regmap_reg_range(AD4134_IFACE_CONFIG_A_REG, AD4134_SILICON_REV_REG), + regmap_reg_range(AD4134_SCRATCH_PAD_REG, AD4134_PW_DOWN_CTRL_REG), + regmap_reg_range(AD4134_DEVICE_STATUS_REG, AD4134_AIN_OR_ERROR_REG), + regmap_reg_range(AD4134_CH_VREG(0), AD4134_CH_VREG(AD4134_NUM_CHANNELS)), +}; + +static const struct regmap_range ad4134_regmap_wr_range[] = { + regmap_reg_range(AD4134_IFACE_CONFIG_A_REG, AD4134_DEVICE_CONFIG_REG), + regmap_reg_range(AD4134_SCRATCH_PAD_REG, AD4134_SCRATCH_PAD_REG), + regmap_reg_range(AD4134_STREAM_MODE_REG, AD4134_PW_DOWN_CTRL_REG), + regmap_reg_range(AD4134_ODR_VAL_INT_LSB_REG, AD4134_CH3_OFFSET_MSB_REG), +}; + +static const struct regmap_access_table ad4134_regmap_rd_table = { + .yes_ranges = ad4134_regmap_rd_range, + .n_yes_ranges = ARRAY_SIZE(ad4134_regmap_rd_range), +}; + +static const struct regmap_access_table ad4134_regmap_wr_table = { + .yes_ranges = ad4134_regmap_wr_range, + .n_yes_ranges = ARRAY_SIZE(ad4134_regmap_wr_range), +}; + +static int ad4134_calc_spi_crc(u8 inst, u8 data) +{ + u8 buf[] = { inst, data }; + + return crc8(ad4134_spi_crc_table, buf, ARRAY_SIZE(buf), + AD4134_SPI_CRC_INIT_VALUE); +} + +static void ad4134_prepare_spi_tx_buf(u8 inst, u8 data, u8 *buf) +{ + buf[0] = inst; + buf[1] = data; + buf[2] = ad4134_calc_spi_crc(inst, data); +} + +static int ad4134_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct ad4134_state *st = context; + struct spi_transfer xfer = { + .tx_buf = st->tx_buf, + .rx_buf = st->rx_buf, + .len = AD4134_SPI_MAX_XFER_LEN, + }; + int ret; + + ad4134_prepare_spi_tx_buf(reg, val, st->tx_buf); + + ret = spi_sync_transfer(st->spi, &xfer, 1); + if (ret) + return ret; + + if (st->rx_buf[2] != st->tx_buf[2]) + dev_dbg(&st->spi->dev, "reg write CRC check failed\n"); + + return 0; +} + +static int ad4134_data_read(struct ad4134_state *st, unsigned int reg, + unsigned int *val) +{ + unsigned int i; + int ret; + + /* + * To be able to read data from all 4 channels through a single line, we + * set DOUTx output format to 0 in the digital interface config register + * (0x12). With that, data from all four channels is serialized and + * output on DOUT0. During the probe, we also set SDO_PIN_SRC_SEL in + * DEVICE_CONFIG_1 register to duplicate DOUT0 on the SDO pin. Combined, + * those configurations enable ADC data read through a conventional SPI + * interface. Now we read data from all channels but keep only the bits + * from the requested one. + */ + for (i = 0; i < ARRAY_SIZE(ad4134_chan_set); i++) { + ret = spi_write_then_read(st->spi, NULL, 0, st->rx_buf, + BITS_TO_BYTES(AD4134_CHAN_PRECISION_BITS)); + if (ret) + return ret; + + /* + * AD4134 has a built-in feature that flags when data transfers + * don't run enough clock cycles to read the entire data frame. + * Clock out data from all channels to avoid that. + */ + if (i == AD4134_VREG_CH(reg)) + *val = get_unaligned_be24(st->rx_buf); + } + + return 0; +} + +static int ad4134_register_read(struct ad4134_state *st, unsigned int reg, + unsigned int *val) +{ + struct spi_transfer xfer = { + .tx_buf = st->tx_buf, + .rx_buf = st->rx_buf, + .len = AD4134_SPI_MAX_XFER_LEN, + }; + unsigned int inst; + int ret; + + inst = AD4134_REG_READ_MASK | reg; + ad4134_prepare_spi_tx_buf(inst, 0, st->tx_buf); + + ret = spi_sync_transfer(st->spi, &xfer, 1); + if (ret) + return ret; + + *val = st->rx_buf[1]; + + /* Check CRC */ + if (st->rx_buf[2] != st->tx_buf[2]) + dev_dbg(&st->spi->dev, "reg read CRC check failed\n"); + + return 0; +} + +static int ad4134_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct ad4134_state *st = context; + + if (reg >= AD4134_CH_VREG(0)) + return ad4134_data_read(st, reg, val); + + return ad4134_register_read(st, reg, val); +} + +static const struct regmap_config ad4134_regmap_config = { + .reg_read = ad4134_reg_read, + .reg_write = ad4134_reg_write, + .rd_table = &ad4134_regmap_rd_table, + .wr_table = &ad4134_regmap_wr_table, + .max_register = AD4134_CH_VREG(ARRAY_SIZE(ad4134_chan_set)), +}; + +static int ad4134_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad4134_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + gpiod_set_value_cansleep(st->odr_gpio, 1); + /* + * For slave mode gated DCLK (data sheet page 11), the minimum + * ODR high time is 3 * tDIGCLK. The internal digital clock + * period is tDIGCLK = 1/fDIGCLK = 2/fSYSCLK. + * The System clock frequency (fSYSCLK) is typically 48 MHz. + * Thus, ODR high time = 3 * (2 / (48 * HZ_PER_MHZ)) + * ODR high time = 0.000000125 s = 125 ns + * 1 micro second should be more than enough. Not worth it + * tweaking for shorter dealy since this is not a fast data path. + */ + fsleep(1); + gpiod_set_value_cansleep(st->odr_gpio, 0); + ret = regmap_read(st->regmap, AD4134_CH_VREG(chan->channel), val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->refin_mv; + *val2 = AD4134_CHAN_PRECISION_BITS - 1; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int ad4134_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct ad4134_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static int ad4134_min_io_mode_setup(struct ad4134_state *st) +{ + struct device *dev = &st->spi->dev; + int ret; + + st->odr_gpio = devm_gpiod_get(dev, "odr", GPIOD_OUT_LOW); + if (IS_ERR(st->odr_gpio)) + return dev_err_probe(dev, PTR_ERR(st->odr_gpio), + "failed to get ODR GPIO\n"); + + ret = regmap_update_bits(st->regmap, AD4134_DIG_IF_CFG_REG, + AD4134_DIF_IF_CFG_FORMAT_MASK, + FIELD_PREP(AD4134_DIF_IF_CFG_FORMAT_MASK, + AD4134_DATA_FORMAT_SINGLE_CH_MODE)); + if (ret) + return dev_err_probe(dev, ret, + "failed to set single channel mode\n"); + + ret = regmap_set_bits(st->regmap, AD4134_SDO_PIN_SRC_SEL_REG, + AD4134_SDO_PIN_SRC_SEL_SDO_SEL_MASK); + if (ret) + return dev_err_probe(dev, ret, + "failed to set SDO source selection\n"); + + return regmap_set_bits(st->regmap, AD4134_IFACE_CONFIG_B_REG, + AD4134_IFACE_CONFIG_B_SINGLE_INSTR); +} + +static const struct iio_info ad4134_info = { + .read_raw = ad4134_read_raw, + .debugfs_reg_access = ad4134_debugfs_reg_access, +}; + +static const char * const ad4143_required_regulators[] = { + "avdd5", "dvdd5", "iovdd", +}; + +static const char * const ad4143_optional_regulators[] = { + "avdd1v8", "dvdd1v8", "clkvdd", +}; + +static int ad4134_regulator_setup(struct ad4134_state *st) +{ + struct device *dev = &st->spi->dev; + int ret; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ad4143_required_regulators), + ad4143_required_regulators); + if (ret) + return dev_err_probe(dev, ret, "failed to enable power supplies\n"); + + /* Required regulator that we need to read the voltage */ + ret = devm_regulator_get_enable_read_voltage(dev, "refin"); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get REFIN voltage.\n"); + + st->refin_mv = ret / (MICRO / MILLI); + + ret = devm_regulator_get_enable_optional(dev, "ldoin"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to enable ldoin supply\n"); + + /* If ldoin was provided, then use the use the internal LDO regulators */ + if (ret == 0) + return 0; + + /* + * If ldoin is not provided, then avdd1v8, dvdd1v8, and clkvdd are + * required. + */ + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ad4143_optional_regulators), + ad4143_optional_regulators); + if (ret) + return dev_err_probe(dev, ret, "failed to enable 1V8 power supplies\n"); + + return 0; +} + +static int ad4134_clock_select(struct ad4134_state *st) +{ + struct device *dev = &st->spi->dev; + struct clk *xtal_clk, *clkin_clk; + + /* + * AD4134 requires one external clock source and only one external clock + * source can be provided at a time. Try to get a crystal provided clock. + * If that fails, try to get a CMOS clock. + */ + xtal_clk = devm_clk_get_optional_enabled(dev, "xtal"); + if (!xtal_clk) + xtal_clk = devm_clk_get_optional_enabled(dev, "xtal"); + if (IS_ERR(xtal_clk)) + return dev_err_probe(dev, PTR_ERR(xtal_clk), + "failed to get xtal\n"); + + clkin_clk = devm_clk_get_optional_enabled(dev, "clkin"); + if (!clkin_clk) + clkin_clk = devm_clk_get_optional_enabled(dev, "clkin"); + if (IS_ERR(clkin_clk)) + return dev_err_probe(dev, PTR_ERR(clkin_clk), + "failed to get clkin\n"); + + st->sys_clk_hz = clk_get_rate(xtal_clk) | clk_get_rate(clkin_clk); + if (st->sys_clk_hz != AD4134_EXT_CLOCK_MHZ) + dev_warn(dev, "invalid external clock frequency %lu\n", + st->sys_clk_hz); + + return 0; +} + +static int ad4134_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct reset_control *rst; + struct iio_dev *indio_dev; + struct ad4134_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + + indio_dev->name = "ad4134"; + indio_dev->channels = ad4134_chan_set; + indio_dev->num_channels = ARRAY_SIZE(ad4134_chan_set); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ad4134_info; + + ret = ad4134_regulator_setup(st); + if (ret) + return ret; + + ret = ad4134_clock_select(st); + if (ret) + return ret; + + rst = devm_reset_control_get_optional_exclusive_deasserted(dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(dev, PTR_ERR(rst), + "failed to get and deassert reset\n"); + + crc8_populate_msb(ad4134_spi_crc_table, AD4134_SPI_CRC_POLYNOM); + + st->regmap = devm_regmap_init(dev, NULL, st, &ad4134_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "failed to initialize regmap"); + + ret = ad4134_min_io_mode_setup(st); + if (ret) + return dev_err_probe(dev, ret, + "failed to setup minimum I/O mode\n"); + + /* Bump precision to 24-bit */ + ret = regmap_update_bits(st->regmap, AD4134_DATA_PACKET_CONFIG_REG, + AD4134_DATA_PACKET_CONFIG_FRAME_MASK, + FIELD_PREP(AD4134_DATA_PACKET_CONFIG_FRAME_MASK, + AD4134_DATA_PACKET_24BIT_FRAME)); + if (ret) + return ret; + + /* Set high performance power mode */ + ret = regmap_update_bits(st->regmap, AD4134_DEVICE_CONFIG_REG, + AD4134_DEVICE_CONFIG_POWER_MODE_MASK, + FIELD_PREP(AD4134_DEVICE_CONFIG_POWER_MODE_MASK, + AD4134_POWER_MODE_HIGH_PERF)); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id ad4134_id[] = { + { "ad4134" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad4134_id); + +static const struct of_device_id ad4134_of_match[] = { + { .compatible = "adi,ad4134" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad4134_of_match); + +static struct spi_driver ad4134_driver = { + .driver = { + .name = "ad4134", + .of_match_table = ad4134_of_match, + }, + .probe = ad4134_probe, + .id_table = ad4134_id, +}; +module_spi_driver(ad4134_driver); + +MODULE_AUTHOR("Marcelo Schmitt "); +MODULE_DESCRIPTION("Analog Devices AD4134 SPI driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_AD4134"); From f7e0867561f0d17630608f57512fbb8da155e1e2 Mon Sep 17 00:00:00 2001 From: Romain Gantois Date: Tue, 9 Dec 2025 09:25:55 +0100 Subject: [PATCH 129/297] iio: dac: ds4424: drop unused include IIO consumer header To prepare for the introduction of namespaced exports for the IIO consumer API, remove this include directive which isn't actually used by the driver. Signed-off-by: Romain Gantois Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ds4424.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/iio/dac/ds4424.c b/drivers/iio/dac/ds4424.c index a8198ba4f98a..6dda8918975a 100644 --- a/drivers/iio/dac/ds4424.c +++ b/drivers/iio/dac/ds4424.c @@ -14,7 +14,6 @@ #include #include #include -#include #define DS4422_MAX_DAC_CHANNELS 2 #define DS4424_MAX_DAC_CHANNELS 4 From a3e2ea7935c5a6f571d43f02b64ebb92e5cfae87 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Fri, 27 Jun 2025 21:37:57 +0200 Subject: [PATCH 130/297] dt-bindings: interconnect: qcom,qcs615-rpmh: Drop IPA interconnects This has been agreed to be characterized as a clock resource, not an interconnect provider. Bring QCS615 in line with the expectation. Signed-off-by: Konrad Dybcio Link: https://lore.kernel.org/r/20250627-topic-qcs615_icc_ipa-v1-3-dc47596cde69@oss.qualcomm.com Signed-off-by: Georgi Djakov --- .../devicetree/bindings/interconnect/qcom,qcs615-rpmh.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Documentation/devicetree/bindings/interconnect/qcom,qcs615-rpmh.yaml b/Documentation/devicetree/bindings/interconnect/qcom,qcs615-rpmh.yaml index 9d762b2a1fcf..e06404828824 100644 --- a/Documentation/devicetree/bindings/interconnect/qcom,qcs615-rpmh.yaml +++ b/Documentation/devicetree/bindings/interconnect/qcom,qcs615-rpmh.yaml @@ -27,7 +27,6 @@ properties: - qcom,qcs615-config-noc - qcom,qcs615-dc-noc - qcom,qcs615-gem-noc - - qcom,qcs615-ipa-virt - qcom,qcs615-mc-virt - qcom,qcs615-mmss-noc - qcom,qcs615-system-noc @@ -46,7 +45,6 @@ allOf: contains: enum: - qcom,qcs615-camnoc-virt - - qcom,qcs615-ipa-virt - qcom,qcs615-mc-virt then: properties: From 06ebbe719bb022795cd9def0606a6805b1f2f755 Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Sat, 10 Jan 2026 18:43:09 +0000 Subject: [PATCH 131/297] interconnect: Add kunit tests for core functionality The interconnect framework currently lacks in-tree unit tests to verify the core logic in isolation. This makes it difficult to validate regression stability when modifying the provider/consumer APIs or aggregation logic. Introduce a kunit test suite that verifies the fundamental behavior of the subsystem. The tests cover: - Provider API (node creation, linking, topology construction). - Consumer API (path enabling/disabling, bandwidth requests). - Standard aggregation logic (accumulating bandwidth across links). - Bulk operations for setting bandwidth on multiple paths. The suite simulates a simple SoC topology with multiple masters and a shared bus to validate traffic aggregation behavior in a controlled software environment, without requiring specific hardware or Device Tree support. Signed-off-by: Kuan-Wei Chiu Link: https://lore.kernel.org/r/20260110184309.906735-1-visitorckw@gmail.com Signed-off-by: Georgi Djakov --- drivers/interconnect/Kconfig | 14 ++ drivers/interconnect/Makefile | 2 + drivers/interconnect/icc-kunit.c | 324 +++++++++++++++++++++++++++++++ 3 files changed, 340 insertions(+) create mode 100644 drivers/interconnect/icc-kunit.c diff --git a/drivers/interconnect/Kconfig b/drivers/interconnect/Kconfig index f2e49bd97d31..882dcb0b4a5b 100644 --- a/drivers/interconnect/Kconfig +++ b/drivers/interconnect/Kconfig @@ -22,4 +22,18 @@ config INTERCONNECT_CLK help Support for wrapping clocks into the interconnect nodes. +config INTERCONNECT_KUNIT_TEST + tristate "KUnit tests for Interconnect framework" + depends on KUNIT + default KUNIT_ALL_TESTS + help + This builds the KUnit test suite for the generic system interconnect + framework. + + The tests cover the core functionality of the interconnect subsystem, + including provider/consumer APIs, topology management, and bandwidth + aggregation logic. + + If unsure, say N. + endif diff --git a/drivers/interconnect/Makefile b/drivers/interconnect/Makefile index b0a9a6753b9d..dc4c7b657c9d 100644 --- a/drivers/interconnect/Makefile +++ b/drivers/interconnect/Makefile @@ -10,3 +10,5 @@ obj-$(CONFIG_INTERCONNECT_QCOM) += qcom/ obj-$(CONFIG_INTERCONNECT_SAMSUNG) += samsung/ obj-$(CONFIG_INTERCONNECT_CLK) += icc-clk.o + +obj-$(CONFIG_INTERCONNECT_KUNIT_TEST) += icc-kunit.o diff --git a/drivers/interconnect/icc-kunit.c b/drivers/interconnect/icc-kunit.c new file mode 100644 index 000000000000..bad2b583737b --- /dev/null +++ b/drivers/interconnect/icc-kunit.c @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit tests for the Interconnect framework. + * + * Copyright (c) 2025 Kuan-Wei Chiu + * + * This suite verifies the behavior of the interconnect core, including + * topology construction, bandwidth aggregation, and path lifecycle. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "internal.h" + +enum { + NODE_CPU, + NODE_GPU, + NODE_BUS, + NODE_DDR, + NODE_MAX +}; + +struct test_node_data { + int id; + const char *name; + int num_links; + int links[2]; +}; + +/* + * Static Topology: + * CPU -\ + * -> BUS -> DDR + * GPU -/ + */ +static const struct test_node_data test_topology[] = { + { NODE_CPU, "cpu", 1, { NODE_BUS } }, + { NODE_GPU, "gpu", 1, { NODE_BUS } }, + { NODE_BUS, "bus", 1, { NODE_DDR } }, + { NODE_DDR, "ddr", 0, { } }, +}; + +struct icc_test_priv { + struct icc_provider provider; + struct platform_device *pdev; + struct icc_node *nodes[NODE_MAX]; +}; + +static struct icc_node *get_node(struct icc_test_priv *priv, int id) +{ + int idx = id - NODE_CPU; + + if (idx < 0 || idx >= ARRAY_SIZE(test_topology)) + return NULL; + return priv->nodes[idx]; +} + +static int icc_test_set(struct icc_node *src, struct icc_node *dst) +{ + return 0; +} + +static int icc_test_aggregate(struct icc_node *node, u32 tag, u32 avg_bw, + u32 peak_bw, u32 *agg_avg, u32 *agg_peak) +{ + return icc_std_aggregate(node, tag, avg_bw, peak_bw, agg_avg, agg_peak); +} + +static struct icc_node *icc_test_xlate(const struct of_phandle_args *spec, void *data) +{ + return NULL; +} + +static int icc_test_get_bw(struct icc_node *node, u32 *avg, u32 *peak) +{ + *avg = 0; + *peak = 0; + + return 0; +} + +static int icc_test_init(struct kunit *test) +{ + struct icc_test_priv *priv; + struct icc_node *node; + int i, j, ret; + + priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); + test->priv = priv; + + priv->pdev = kunit_platform_device_alloc(test, "icc-test-dev", -1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->pdev); + KUNIT_ASSERT_EQ(test, kunit_platform_device_add(test, priv->pdev), 0); + + priv->provider.set = icc_test_set; + priv->provider.aggregate = icc_test_aggregate; + priv->provider.xlate = icc_test_xlate; + priv->provider.get_bw = icc_test_get_bw; + priv->provider.dev = &priv->pdev->dev; + priv->provider.data = priv; + INIT_LIST_HEAD(&priv->provider.nodes); + + ret = icc_provider_register(&priv->provider); + KUNIT_ASSERT_EQ(test, ret, 0); + + for (i = 0; i < ARRAY_SIZE(test_topology); i++) { + const struct test_node_data *data = &test_topology[i]; + + node = icc_node_create(data->id); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node); + + node->name = data->name; + icc_node_add(node, &priv->provider); + priv->nodes[i] = node; + } + + for (i = 0; i < ARRAY_SIZE(test_topology); i++) { + const struct test_node_data *data = &test_topology[i]; + struct icc_node *src = get_node(priv, data->id); + + for (j = 0; j < data->num_links; j++) { + ret = icc_link_create(src, data->links[j]); + KUNIT_ASSERT_EQ_MSG(test, ret, 0, "Failed to link %s->%d", + src->name, data->links[j]); + } + } + + icc_sync_state(&priv->pdev->dev); + + return 0; +} + +static void icc_test_exit(struct kunit *test) +{ + struct icc_test_priv *priv = test->priv; + + icc_nodes_remove(&priv->provider); + icc_provider_deregister(&priv->provider); +} + +/* + * Helper to construct a mock path. + * + * Because we are bypassing icc_get(), we must manually link the requests + * to the nodes' req_list so that icc_std_aggregate() can discover them. + */ +static struct icc_path *icc_test_create_path(struct kunit *test, + struct icc_node **nodes, int num) +{ + struct icc_path *path; + int i; + + path = kunit_kzalloc(test, struct_size(path, reqs, num), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, path); + + path->num_nodes = num; + for (i = 0; i < num; i++) { + path->reqs[i].node = nodes[i]; + hlist_add_head(&path->reqs[i].req_node, &nodes[i]->req_list); + } + path->name = "mock-path"; + + return path; +} + +static void icc_test_destroy_path(struct kunit *test, struct icc_path *path) +{ + int i; + + for (i = 0; i < path->num_nodes; i++) + hlist_del(&path->reqs[i].req_node); + + kunit_kfree(test, path); +} + +static void icc_test_topology_integrity(struct kunit *test) +{ + struct icc_test_priv *priv = test->priv; + struct icc_node *cpu = get_node(priv, NODE_CPU); + struct icc_node *bus = get_node(priv, NODE_BUS); + + KUNIT_EXPECT_EQ(test, cpu->num_links, 1); + KUNIT_EXPECT_PTR_EQ(test, cpu->links[0], bus); + KUNIT_EXPECT_PTR_EQ(test, cpu->provider, &priv->provider); +} + +static void icc_test_set_bw(struct kunit *test) +{ + struct icc_test_priv *priv = test->priv; + struct icc_path *path; + struct icc_node *path_nodes[3]; + int ret; + + /* Path: CPU -> BUS -> DDR */ + path_nodes[0] = get_node(priv, NODE_CPU); + path_nodes[1] = get_node(priv, NODE_BUS); + path_nodes[2] = get_node(priv, NODE_DDR); + + path = icc_test_create_path(test, path_nodes, 3); + + ret = icc_enable(path); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = icc_set_bw(path, 1000, 2000); + KUNIT_EXPECT_EQ(test, ret, 0); + + KUNIT_EXPECT_EQ(test, path_nodes[0]->avg_bw, 1000); + KUNIT_EXPECT_EQ(test, path_nodes[0]->peak_bw, 2000); + KUNIT_EXPECT_EQ(test, path_nodes[1]->avg_bw, 1000); + KUNIT_EXPECT_EQ(test, path_nodes[1]->peak_bw, 2000); + + icc_set_tag(path, 0xABC); + KUNIT_EXPECT_EQ(test, path->reqs[0].tag, 0xABC); + + icc_disable(path); + KUNIT_EXPECT_EQ(test, path_nodes[0]->avg_bw, 0); + + icc_test_destroy_path(test, path); +} + +static void icc_test_aggregation(struct kunit *test) +{ + struct icc_test_priv *priv = test->priv; + struct icc_path *path_cpu, *path_gpu; + struct icc_node *nodes_cpu[3], *nodes_gpu[2]; + struct icc_node *bus = get_node(priv, NODE_BUS); + int ret; + + nodes_cpu[0] = get_node(priv, NODE_CPU); + nodes_cpu[1] = bus; + nodes_cpu[2] = get_node(priv, NODE_DDR); + path_cpu = icc_test_create_path(test, nodes_cpu, 3); + + nodes_gpu[0] = get_node(priv, NODE_GPU); + nodes_gpu[1] = bus; + path_gpu = icc_test_create_path(test, nodes_gpu, 2); + + icc_enable(path_cpu); + icc_enable(path_gpu); + + ret = icc_set_bw(path_cpu, 1000, 1000); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, bus->avg_bw, 1000); + + ret = icc_set_bw(path_gpu, 2000, 2000); + KUNIT_EXPECT_EQ(test, ret, 0); + + /* Bus aggregates: CPU(1000) + GPU(2000) */ + KUNIT_EXPECT_EQ(test, bus->avg_bw, 3000); + /* Peak aggregates: max(CPU, GPU) */ + KUNIT_EXPECT_EQ(test, bus->peak_bw, 2000); + + icc_test_destroy_path(test, path_cpu); + icc_test_destroy_path(test, path_gpu); +} + +static void icc_test_bulk_ops(struct kunit *test) +{ + struct icc_test_priv *priv = test->priv; + struct icc_node *nodes_cpu[3], *nodes_gpu[2]; + struct icc_bulk_data bulk[2]; + int ret; + + nodes_cpu[0] = get_node(priv, NODE_CPU); + nodes_cpu[1] = get_node(priv, NODE_BUS); + nodes_cpu[2] = get_node(priv, NODE_DDR); + + nodes_gpu[0] = get_node(priv, NODE_GPU); + nodes_gpu[1] = get_node(priv, NODE_BUS); + + bulk[0].path = icc_test_create_path(test, nodes_cpu, 3); + bulk[0].avg_bw = 500; + bulk[0].peak_bw = 500; + + bulk[1].path = icc_test_create_path(test, nodes_gpu, 2); + bulk[1].avg_bw = 600; + bulk[1].peak_bw = 600; + + ret = icc_bulk_set_bw(2, bulk); + KUNIT_EXPECT_EQ(test, ret, 0); + /* Paths disabled, bandwidth should be 0 */ + KUNIT_EXPECT_EQ(test, get_node(priv, NODE_BUS)->avg_bw, 0); + + ret = icc_bulk_enable(2, bulk); + KUNIT_EXPECT_EQ(test, ret, 0); + /* Paths enabled, aggregation applies */ + KUNIT_EXPECT_EQ(test, get_node(priv, NODE_BUS)->avg_bw, 1100); + + icc_bulk_disable(2, bulk); + KUNIT_EXPECT_EQ(test, get_node(priv, NODE_BUS)->avg_bw, 0); + + icc_test_destroy_path(test, bulk[0].path); + icc_test_destroy_path(test, bulk[1].path); +} + +static struct kunit_case icc_test_cases[] = { + KUNIT_CASE(icc_test_topology_integrity), + KUNIT_CASE(icc_test_set_bw), + KUNIT_CASE(icc_test_aggregation), + KUNIT_CASE(icc_test_bulk_ops), + {} +}; + +static struct kunit_suite icc_test_suite = { + .name = "interconnect", + .init = icc_test_init, + .exit = icc_test_exit, + .test_cases = icc_test_cases, +}; + +kunit_test_suite(icc_test_suite); + +MODULE_AUTHOR("Kuan-Wei Chiu "); +MODULE_DESCRIPTION("KUnit tests for the Interconnect framework"); +MODULE_LICENSE("GPL"); From 1ec5e098ef5f9cefdaccca3747b5e3802fb8b2bf Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Wed, 14 Jan 2026 09:51:37 +0200 Subject: [PATCH 132/297] iio: pressure: abp2030pa: fix typo in Kconfig description Replace "I2C" with "SPI" in the SPI module description. Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/pressure/Kconfig b/drivers/iio/pressure/Kconfig index 0d25842aa414..838a8340c4c0 100644 --- a/drivers/iio/pressure/Kconfig +++ b/drivers/iio/pressure/Kconfig @@ -38,7 +38,7 @@ config ABP2030PA_SPI depends on SPI_MASTER select ABP2030PA help - Say Y here to build I2C bus support for the Honeywell ABP2 + Say Y here to build SPI bus support for the Honeywell ABP2 series pressure and temperature digital sensor. To compile this driver as a module, choose M here: the module From b82f3047dae4aba38cb26c55c28444db4d77f521 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Wed, 14 Jan 2026 09:51:38 +0200 Subject: [PATCH 133/297] iio: pressure: abp2030pa: remove error message Do not print a duplicate error message if devm_request_irq() fails. Reviewed-by: Andy Shevchenko Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/abp2030pa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/pressure/abp2030pa.c b/drivers/iio/pressure/abp2030pa.c index be11e28208ec..4ca056a73cef 100644 --- a/drivers/iio/pressure/abp2030pa.c +++ b/drivers/iio/pressure/abp2030pa.c @@ -523,7 +523,7 @@ int abp2_common_probe(struct device *dev, const struct abp2_ops *ops, int irq) ret = devm_request_irq(dev, irq, abp2_eoc_handler, IRQF_ONESHOT, dev_name(dev), data); if (ret) - return dev_err_probe(dev, ret, "request irq %d failed\n", data->irq); + return ret; } ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, From b0e930a6360ff5f81045146f0a8fcac4f7897eda Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Tue, 13 Jan 2026 09:25:58 +0000 Subject: [PATCH 134/297] dt-bindings: misc: google,android-pipe: Convert to DT schema Convert the Android Goldfish QEMU Pipe binding to DT schema format. Move the file to the misc directory as it represents a miscellaneous communication device. Update the example node name to 'pipe' to comply with generic node naming standards and fix the mismatch between unit address and reg property in the original example. Signed-off-by: Kuan-Wei Chiu Reviewed-by: Krzysztof Kozlowski Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260113092602.3197681-3-visitorckw@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/goldfish/pipe.txt | 17 --------- .../bindings/misc/google,android-pipe.yaml | 38 +++++++++++++++++++ 2 files changed, 38 insertions(+), 17 deletions(-) delete mode 100644 Documentation/devicetree/bindings/goldfish/pipe.txt create mode 100644 Documentation/devicetree/bindings/misc/google,android-pipe.yaml diff --git a/Documentation/devicetree/bindings/goldfish/pipe.txt b/Documentation/devicetree/bindings/goldfish/pipe.txt deleted file mode 100644 index 5637ce701788..000000000000 --- a/Documentation/devicetree/bindings/goldfish/pipe.txt +++ /dev/null @@ -1,17 +0,0 @@ -Android Goldfish QEMU Pipe - -Android pipe virtual device generated by android emulator. - -Required properties: - -- compatible : should contain "google,android-pipe" to match emulator -- reg : -- interrupts : - -Example: - - android_pipe@a010000 { - compatible = "google,android-pipe"; - reg = ; - interrupts = <0x12>; - }; diff --git a/Documentation/devicetree/bindings/misc/google,android-pipe.yaml b/Documentation/devicetree/bindings/misc/google,android-pipe.yaml new file mode 100644 index 000000000000..9e8046fd358d --- /dev/null +++ b/Documentation/devicetree/bindings/misc/google,android-pipe.yaml @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/misc/google,android-pipe.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Android Goldfish QEMU Pipe + +maintainers: + - Kuan-Wei Chiu + +description: + Android QEMU pipe virtual device generated by Android emulator. + +properties: + compatible: + const: google,android-pipe + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + +additionalProperties: false + +examples: + - | + pipe@ff018000 { + compatible = "google,android-pipe"; + reg = <0xff018000 0x2000>; + interrupts = <18>; + }; From 68aabb29a5469e4b7358e70e64a7fac433e27f06 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Mon, 5 Jan 2026 14:25:03 +0000 Subject: [PATCH 135/297] rust: redefine `bindings::compat_ptr_ioctl` in Rust There is currently an inconsistency between C and Rust, which is that when Rust requires cfg(CONFIG_COMPAT) on compat_ioctl when using the compat_ptr_ioctl symbol because '#define compat_ptr_ioctl NULL' does not get translated to anything by bindgen. But it's not *just* a matter of translating the '#define' into Rust when CONFIG_COMPAT=n. This is because when CONFIG_COMPAT=y, the type of compat_ptr_ioctl is a non-nullable function pointer, and to seamlessly use it regardless of the config, we need a nullable function pointer. I think it's important to do something about this; I've seen the mistake of accidentally forgetting '#[cfg(CONFIG_COMPAT)]' when compat_ptr_ioctl is used multiple times now. This explicitly declares 'bindings::compat_ptr_ioctl' as an Option that is always defined but might be None. This matches C, but isn't ideal: it modifies the bindings crate. But I'm not sure if there's a better way to do it. If we just redefine in kernel/, then people may still use the one in bindings::, since that is where you would normally find it. I am open to suggestions. Signed-off-by: Alice Ryhl Reviewed-by: Gary Guo Link: https://patch.msgid.link/20260105-redefine-compat_ptr_ioctl-v1-1-25edb3d91acc@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/rust_binder_main.rs | 3 +-- rust/bindings/lib.rs | 13 +++++++++++++ rust/kernel/miscdevice.rs | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs index d84c3c360be0..30e4517f90a3 100644 --- a/drivers/android/binder/rust_binder_main.rs +++ b/drivers/android/binder/rust_binder_main.rs @@ -322,8 +322,7 @@ pub static rust_binder_fops: AssertSync = { owner: THIS_MODULE.as_ptr(), poll: Some(rust_binder_poll), unlocked_ioctl: Some(rust_binder_ioctl), - #[cfg(CONFIG_COMPAT)] - compat_ioctl: Some(bindings::compat_ptr_ioctl), + compat_ioctl: bindings::compat_ptr_ioctl, mmap: Some(rust_binder_mmap), open: Some(rust_binder_open), release: Some(rust_binder_release), diff --git a/rust/bindings/lib.rs b/rust/bindings/lib.rs index 0c57cf9b4004..19f57c5b2fa2 100644 --- a/rust/bindings/lib.rs +++ b/rust/bindings/lib.rs @@ -67,3 +67,16 @@ mod bindings_helper { } pub use bindings_raw::*; + +pub const compat_ptr_ioctl: Option< + unsafe extern "C" fn(*mut file, ffi::c_uint, ffi::c_ulong) -> ffi::c_long, +> = { + #[cfg(CONFIG_COMPAT)] + { + Some(bindings_raw::compat_ptr_ioctl) + } + #[cfg(not(CONFIG_COMPAT))] + { + None + } +}; diff --git a/rust/kernel/miscdevice.rs b/rust/kernel/miscdevice.rs index ba64c8a858f0..c3c2052c9206 100644 --- a/rust/kernel/miscdevice.rs +++ b/rust/kernel/miscdevice.rs @@ -410,7 +410,7 @@ impl MiscdeviceVTable { compat_ioctl: if T::HAS_COMPAT_IOCTL { Some(Self::compat_ioctl) } else if T::HAS_IOCTL { - Some(bindings::compat_ptr_ioctl) + bindings::compat_ptr_ioctl } else { None }, From 56d21267663bad91e8b10121224ec46366a7937e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 7 Jan 2026 15:29:50 +0100 Subject: [PATCH 136/297] binder: don't use %pK through printk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the past %pK was preferable to %p as it would not leak raw pointer values into the kernel log. Since commit ad67b74d2469 ("printk: hash addresses printed with %p") the regular %p has been improved to avoid this issue. Furthermore, restricted pointers ("%pK") were never meant to be used through printk(). They can still unintentionally leak raw pointers or acquire sleeping locks in atomic contexts. Switch to the regular pointer formatting which is safer and easier to reason about. There are still a few users of %pK left, but these use it through seq_file, for which its usage is safe. Signed-off-by: Thomas Weißschuh Acked-by: Carlos Llamas Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20260107-restricted-pointers-binder-v1-1-181018bf3812@linutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder.c | 2 +- drivers/android/binder_alloc.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/android/binder.c b/drivers/android/binder.c index 535fc881c8da..adde1e40cccd 100644 --- a/drivers/android/binder.c +++ b/drivers/android/binder.c @@ -4510,7 +4510,7 @@ static int binder_thread_write(struct binder_proc *proc, } } binder_debug(BINDER_DEBUG_DEAD_BINDER, - "%d:%d BC_DEAD_BINDER_DONE %016llx found %pK\n", + "%d:%d BC_DEAD_BINDER_DONE %016llx found %p\n", proc->pid, thread->pid, (u64)cookie, death); if (death == NULL) { diff --git a/drivers/android/binder_alloc.c b/drivers/android/binder_alloc.c index 979c96b74cad..d5ed64543bbf 100644 --- a/drivers/android/binder_alloc.c +++ b/drivers/android/binder_alloc.c @@ -81,7 +81,7 @@ static void binder_insert_free_buffer(struct binder_alloc *alloc, new_buffer_size = binder_alloc_buffer_size(alloc, new_buffer); binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC, - "%d: add free buffer, size %zd, at %pK\n", + "%d: add free buffer, size %zd, at %p\n", alloc->pid, new_buffer_size, new_buffer); while (*p) { @@ -572,7 +572,7 @@ static struct binder_buffer *binder_alloc_new_buf_locked( } binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC, - "%d: binder_alloc_buf size %zd got buffer %pK size %zd\n", + "%d: binder_alloc_buf size %zd got buffer %p size %zd\n", alloc->pid, size, buffer, buffer_size); /* @@ -748,7 +748,7 @@ static void binder_free_buf_locked(struct binder_alloc *alloc, ALIGN(buffer->extra_buffers_size, sizeof(void *)); binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC, - "%d: binder_free_buf %pK size %zd buffer_size %zd\n", + "%d: binder_free_buf %p size %zd buffer_size %zd\n", alloc->pid, buffer, size, buffer_size); BUG_ON(buffer->free); From 25f9b0d351552f63ac3f72e6c655bc8f9994f7d2 Mon Sep 17 00:00:00 2001 From: Simon Richter Date: Thu, 8 Jan 2026 03:26:02 +0900 Subject: [PATCH 137/297] misc/mei: Allow building Intel ME interface on non-x86 The xe driver supports dGPUs which can be plugged into non-x86 machines, and exposes a MEI GSC interface, so this driver is no longer x86 only. Cc: Usyskin, Alexander Cc: Greg Kroah-Hartman Signed-off-by: Simon Richter Link: https://patch.msgid.link/20260107182615.488194-2-Simon.Richter@hogyros.de Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/mei/Kconfig b/drivers/misc/mei/Kconfig index f4eb307cd35e..6da518f3b73c 100644 --- a/drivers/misc/mei/Kconfig +++ b/drivers/misc/mei/Kconfig @@ -2,7 +2,7 @@ # Copyright (c) 2003-2019, Intel Corporation. All rights reserved. config INTEL_MEI tristate "Intel Management Engine Interface" - depends on X86 && PCI + depends on PCI default X86_64 || MATOM help The Intel Management Engine (Intel ME) provides Manageability, From d876cb978058a57b5d28b2b3e16197f678ce8311 Mon Sep 17 00:00:00 2001 From: Simon Richter Date: Thu, 8 Jan 2026 03:26:03 +0900 Subject: [PATCH 138/297] misc/mei: Decouple ME interfaces from GPU drivers These are enumerated via an auxiliary bus, so there is no functional dependency between these drivers, therefore allow compiling MEI as builtin even when i915/xe are built as modules. Cc: Usyskin, Alexander Cc: Greg Kroah-Hartman Signed-off-by: Simon Richter Link: https://patch.msgid.link/20260107182615.488194-3-Simon.Richter@hogyros.de Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/Kconfig | 4 ++-- drivers/misc/mei/gsc_proxy/Kconfig | 2 +- drivers/misc/mei/hdcp/Kconfig | 2 +- drivers/misc/mei/pxp/Kconfig | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/misc/mei/Kconfig b/drivers/misc/mei/Kconfig index 6da518f3b73c..a62992909f85 100644 --- a/drivers/misc/mei/Kconfig +++ b/drivers/misc/mei/Kconfig @@ -49,7 +49,7 @@ config INTEL_MEI_TXE config INTEL_MEI_GSC tristate "Intel MEI GSC embedded device" depends on INTEL_MEI_ME - depends on DRM_I915 || DRM_XE + depends on DRM_I915!=n || DRM_XE!=n help Intel auxiliary driver for GSC devices embedded in Intel graphics devices. @@ -84,7 +84,7 @@ config INTEL_MEI_VSC config INTEL_MEI_LB tristate "Intel Late Binding (LB) support on ME Interface" depends on INTEL_MEI_ME - depends on DRM_XE + depends on DRM_XE!=n help Enable support for Intel Late Binding (LB) via the MEI interface. diff --git a/drivers/misc/mei/gsc_proxy/Kconfig b/drivers/misc/mei/gsc_proxy/Kconfig index ac78b9d1eccd..30811117fc65 100644 --- a/drivers/misc/mei/gsc_proxy/Kconfig +++ b/drivers/misc/mei/gsc_proxy/Kconfig @@ -4,7 +4,7 @@ config INTEL_MEI_GSC_PROXY tristate "Intel GSC Proxy services of ME Interface" depends on INTEL_MEI_ME - depends on DRM_I915 + depends on DRM_I915!=n help MEI Support for GSC Proxy Services on Intel platforms. diff --git a/drivers/misc/mei/hdcp/Kconfig b/drivers/misc/mei/hdcp/Kconfig index 631dd9651d7c..a9af4918e5b2 100644 --- a/drivers/misc/mei/hdcp/Kconfig +++ b/drivers/misc/mei/hdcp/Kconfig @@ -4,7 +4,7 @@ config INTEL_MEI_HDCP tristate "Intel HDCP2.2 services of ME Interface" depends on INTEL_MEI_ME - depends on DRM_I915 || DRM_XE + depends on DRM_I915!=n || DRM_XE!=n help MEI Support for HDCP2.2 Services on Intel platforms. diff --git a/drivers/misc/mei/pxp/Kconfig b/drivers/misc/mei/pxp/Kconfig index aa2dece4a927..d0f8bb6aa2de 100644 --- a/drivers/misc/mei/pxp/Kconfig +++ b/drivers/misc/mei/pxp/Kconfig @@ -4,7 +4,7 @@ config INTEL_MEI_PXP tristate "Intel PXP services of ME Interface" depends on INTEL_MEI_ME - depends on DRM_I915 || DRM_XE + depends on DRM_I915!=n || DRM_XE!=n help MEI Support for PXP Services on Intel platforms. From 6d5dca5f9e37741f5e50c31c81b497f875dca6c8 Mon Sep 17 00:00:00 2001 From: Simon Richter Date: Thu, 8 Jan 2026 03:26:04 +0900 Subject: [PATCH 139/297] misc/mei: Allow building standalone for compile testing While this is not a particularly useful configuration, the MEI code should compile even when no drivers for a GPU containing a management engine are built. Cc: Usyskin, Alexander Cc: Greg Kroah-Hartman Signed-off-by: Simon Richter Link: https://patch.msgid.link/20260107182615.488194-4-Simon.Richter@hogyros.de Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/Kconfig | 4 ++-- drivers/misc/mei/gsc_proxy/Kconfig | 2 +- drivers/misc/mei/hdcp/Kconfig | 2 +- drivers/misc/mei/pxp/Kconfig | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/misc/mei/Kconfig b/drivers/misc/mei/Kconfig index a62992909f85..5902dd1ee44b 100644 --- a/drivers/misc/mei/Kconfig +++ b/drivers/misc/mei/Kconfig @@ -49,7 +49,7 @@ config INTEL_MEI_TXE config INTEL_MEI_GSC tristate "Intel MEI GSC embedded device" depends on INTEL_MEI_ME - depends on DRM_I915!=n || DRM_XE!=n + depends on DRM_I915!=n || DRM_XE!=n || COMPILE_TEST help Intel auxiliary driver for GSC devices embedded in Intel graphics devices. @@ -84,7 +84,7 @@ config INTEL_MEI_VSC config INTEL_MEI_LB tristate "Intel Late Binding (LB) support on ME Interface" depends on INTEL_MEI_ME - depends on DRM_XE!=n + depends on DRM_XE!=n || COMPILE_TEST help Enable support for Intel Late Binding (LB) via the MEI interface. diff --git a/drivers/misc/mei/gsc_proxy/Kconfig b/drivers/misc/mei/gsc_proxy/Kconfig index 30811117fc65..b80024c5189b 100644 --- a/drivers/misc/mei/gsc_proxy/Kconfig +++ b/drivers/misc/mei/gsc_proxy/Kconfig @@ -4,7 +4,7 @@ config INTEL_MEI_GSC_PROXY tristate "Intel GSC Proxy services of ME Interface" depends on INTEL_MEI_ME - depends on DRM_I915!=n + depends on DRM_I915!=n || COMPILE_TEST help MEI Support for GSC Proxy Services on Intel platforms. diff --git a/drivers/misc/mei/hdcp/Kconfig b/drivers/misc/mei/hdcp/Kconfig index a9af4918e5b2..b9d5205c5b1a 100644 --- a/drivers/misc/mei/hdcp/Kconfig +++ b/drivers/misc/mei/hdcp/Kconfig @@ -4,7 +4,7 @@ config INTEL_MEI_HDCP tristate "Intel HDCP2.2 services of ME Interface" depends on INTEL_MEI_ME - depends on DRM_I915!=n || DRM_XE!=n + depends on DRM_I915!=n || DRM_XE!=n || COMPILE_TEST help MEI Support for HDCP2.2 Services on Intel platforms. diff --git a/drivers/misc/mei/pxp/Kconfig b/drivers/misc/mei/pxp/Kconfig index d0f8bb6aa2de..2c5c00dc4b6f 100644 --- a/drivers/misc/mei/pxp/Kconfig +++ b/drivers/misc/mei/pxp/Kconfig @@ -4,7 +4,7 @@ config INTEL_MEI_PXP tristate "Intel PXP services of ME Interface" depends on INTEL_MEI_ME - depends on DRM_I915!=n || DRM_XE!=n + depends on DRM_I915!=n || DRM_XE!=n || COMPILE_TEST help MEI Support for PXP Services on Intel platforms. From 327e987e5166a407554e8cccba8e5f8d51f1c868 Mon Sep 17 00:00:00 2001 From: Simon Richter Date: Thu, 8 Jan 2026 03:26:05 +0900 Subject: [PATCH 140/297] misc/mei: gsc_proxy: add dependency on Xe driver This driver is useful if at least one DRM driver registers an auxiliary device for the ME interface. With the addition of Xe, this is no longer just i915. Cc: Usyskin, Alexander Cc: Greg Kroah-Hartman Signed-off-by: Simon Richter Link: https://patch.msgid.link/20260107182615.488194-5-Simon.Richter@hogyros.de Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/gsc_proxy/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/mei/gsc_proxy/Kconfig b/drivers/misc/mei/gsc_proxy/Kconfig index b80024c5189b..bd8f955f548e 100644 --- a/drivers/misc/mei/gsc_proxy/Kconfig +++ b/drivers/misc/mei/gsc_proxy/Kconfig @@ -4,7 +4,7 @@ config INTEL_MEI_GSC_PROXY tristate "Intel GSC Proxy services of ME Interface" depends on INTEL_MEI_ME - depends on DRM_I915!=n || COMPILE_TEST + depends on DRM_I915!=n || DRM_XE!=n || COMPILE_TEST help MEI Support for GSC Proxy Services on Intel platforms. From 96118565d24e7691e423d73be224b3a3fffc4680 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 28 Nov 2025 10:17:49 +0300 Subject: [PATCH 141/297] gpib: Fix error code in ibonline() This accidentally returns 1 on error, but it should return negative error codes. Fixes: 9dde4559e939 ("staging: gpib: Add GPIB common core driver") Signed-off-by: Dan Carpenter Link: https://patch.msgid.link/aSlMnaT1M104NJb2@stanley.mountain Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/common/iblib.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/gpib/common/iblib.c b/drivers/gpib/common/iblib.c index 7cbb6a467177..b672dd6aad25 100644 --- a/drivers/gpib/common/iblib.c +++ b/drivers/gpib/common/iblib.c @@ -227,11 +227,10 @@ int ibonline(struct gpib_board *board) #ifndef CONFIG_NIOS2 board->autospoll_task = kthread_run(&autospoll_thread, board, "gpib%d_autospoll_kthread", board->minor); - retval = IS_ERR(board->autospoll_task); - if (retval) { + if (IS_ERR(board->autospoll_task)) { dev_err(board->gpib_dev, "failed to create autospoll thread\n"); board->interface->detach(board); - return retval; + return PTR_ERR(board->autospoll_task); } #endif board->online = 1; From 484e62252212c5b5fc62eaee5e4977143cb159c6 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 28 Nov 2025 10:17:57 +0300 Subject: [PATCH 142/297] gpib: Fix error code in ni_usb_write_registers() If ni_usb_receive_bulk_msg() succeeds but without reading 16 bytes, then the error code needs to be set. The current code returns success. Fixes: 4e127de14fa7 ("staging: gpib: Add National Instruments USB GPIB driver") Signed-off-by: Dan Carpenter Link: https://patch.msgid.link/aSlMpbE4IrQuBGFS@stanley.mountain Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/ni_usb/ni_usb_gpib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpib/ni_usb/ni_usb_gpib.c b/drivers/gpib/ni_usb/ni_usb_gpib.c index 1f8412de9fa3..fdcaa6c00bfe 100644 --- a/drivers/gpib/ni_usb/ni_usb_gpib.c +++ b/drivers/gpib/ni_usb/ni_usb_gpib.c @@ -566,7 +566,7 @@ static int ni_usb_write_registers(struct ni_usb_priv *ni_priv, retval, bytes_read); ni_usb_dump_raw_block(in_data, bytes_read); kfree(in_data); - return retval; + return retval ?: -EINVAL; } mutex_unlock(&ni_priv->addressed_transfer_lock); From b89921eed8cf2d97250bac4be38dbcfbf048b586 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Tue, 30 Dec 2025 03:45:46 +0000 Subject: [PATCH 143/297] gpib: Fix memory leak in ni_usb_init() In ni_usb_init(), if ni_usb_setup_init() fails, the function returns -EFAULT without freeing the allocated writes buffer, leading to a memory leak. Additionally, ni_usb_setup_init() returns 0 on failure, which causes ni_usb_init() to return -EFAULT, an inappropriate error code for this situation. Fix the leak by freeing writes in the error path. Modify ni_usb_setup_init() to return -EINVAL on failure and propagate this error code in ni_usb_init(). Fixes: 4e127de14fa7 ("staging: gpib: Add National Instruments USB GPIB driver") Suggested-by: Greg KH Suggested-by: Dave Penkler Co-developed-by: Jianhao Xu Signed-off-by: Jianhao Xu Signed-off-by: Zilin Guan Link: https://patch.msgid.link/20251230034546.929452-1-zilin@seu.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/ni_usb/ni_usb_gpib.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/gpib/ni_usb/ni_usb_gpib.c b/drivers/gpib/ni_usb/ni_usb_gpib.c index fdcaa6c00bfe..b6fddb437f55 100644 --- a/drivers/gpib/ni_usb/ni_usb_gpib.c +++ b/drivers/gpib/ni_usb/ni_usb_gpib.c @@ -1780,7 +1780,7 @@ static int ni_usb_setup_init(struct gpib_board *board, struct ni_usb_register *w i++; if (i > NUM_INIT_WRITES) { dev_err(&usb_dev->dev, "bug!, buffer overrun, i=%i\n", i); - return 0; + return -EINVAL; } return i; } @@ -1799,10 +1799,12 @@ static int ni_usb_init(struct gpib_board *board) return -ENOMEM; writes_len = ni_usb_setup_init(board, writes); - if (writes_len) - retval = ni_usb_write_registers(ni_priv, writes, writes_len, &ibsta); - else - return -EFAULT; + if (writes_len < 0) { + kfree(writes); + return writes_len; + } + + retval = ni_usb_write_registers(ni_priv, writes, writes_len, &ibsta); kfree(writes); if (retval) { dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); From 986d388e6779800080d13b555c9aaf6900614387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 13 Jan 2026 15:50:09 +0100 Subject: [PATCH 144/297] greybus: Use bus methods for .probe() and .remove() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are nearly identical to the respective driver callbacks. The only difference is that .remove() returns void instead of int. The objective is to get rid of users of struct device_driver callbacks .probe() and .remove() to eventually remove these. Acked-by: Johan Hovold Signed-off-by: Uwe Kleine-König Link: https://patch.msgid.link/20260113145012.2379944-2-u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/greybus/core.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/drivers/greybus/core.c b/drivers/greybus/core.c index 313eb65cf703..45c5437c460f 100644 --- a/drivers/greybus/core.c +++ b/drivers/greybus/core.c @@ -185,13 +185,6 @@ static void greybus_shutdown(struct device *dev) } } -const struct bus_type greybus_bus_type = { - .name = "greybus", - .match = greybus_match_device, - .uevent = greybus_uevent, - .shutdown = greybus_shutdown, -}; - static int greybus_probe(struct device *dev) { struct greybus_driver *driver = to_greybus_driver(dev->driver); @@ -252,7 +245,7 @@ static int greybus_probe(struct device *dev) return 0; } -static int greybus_remove(struct device *dev) +static void greybus_remove(struct device *dev) { struct greybus_driver *driver = to_greybus_driver(dev->driver); struct gb_bundle *bundle = to_gb_bundle(dev); @@ -291,10 +284,17 @@ static int greybus_remove(struct device *dev) pm_runtime_set_suspended(dev); pm_runtime_dont_use_autosuspend(dev); pm_runtime_put_noidle(dev); - - return 0; } +const struct bus_type greybus_bus_type = { + .name = "greybus", + .match = greybus_match_device, + .uevent = greybus_uevent, + .probe = greybus_probe, + .remove = greybus_remove, + .shutdown = greybus_shutdown, +}; + int greybus_register_driver(struct greybus_driver *driver, struct module *owner, const char *mod_name) { @@ -305,8 +305,6 @@ int greybus_register_driver(struct greybus_driver *driver, struct module *owner, driver->driver.bus = &greybus_bus_type; driver->driver.name = driver->name; - driver->driver.probe = greybus_probe; - driver->driver.remove = greybus_remove; driver->driver.owner = owner; driver->driver.mod_name = mod_name; From 45edeece5abe146b1188ef9af3cde0609997493b Mon Sep 17 00:00:00 2001 From: "Christophe Leroy (CS GROUP)" Date: Fri, 16 Jan 2026 13:23:33 +0100 Subject: [PATCH 145/297] bus: fsl-mc: declare fsl_mc_bus_dpdbg_type static Fix following sparse warning: CHECK drivers/bus/fsl-mc/fsl-mc-bus.c drivers/bus/fsl-mc/fsl-mc-bus.c:435:26: warning: symbol 'fsl_mc_bus_dpdbg_type' was not declared. Should it be static? fsl_mc_bus_dpdbg_type is not used outside of fsl-mc-bus.c Remove the EXPORT_SYMBOL and declare it static. Signed-off-by: Christophe Leroy (CS GROUP) Link: https://patch.msgid.link/628c49881b3a1df76cfd2f8fd2aad976692a465a.1768566053.git.chleroy@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/bus/fsl-mc/fsl-mc-bus.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/bus/fsl-mc/fsl-mc-bus.c b/drivers/bus/fsl-mc/fsl-mc-bus.c index 25845c04e562..d1ab4b0869ca 100644 --- a/drivers/bus/fsl-mc/fsl-mc-bus.c +++ b/drivers/bus/fsl-mc/fsl-mc-bus.c @@ -396,10 +396,9 @@ const struct device_type fsl_mc_bus_dpdmai_type = { }; EXPORT_SYMBOL_GPL(fsl_mc_bus_dpdmai_type); -const struct device_type fsl_mc_bus_dpdbg_type = { +static const struct device_type fsl_mc_bus_dpdbg_type = { .name = "fsl_mc_bus_dpdbg" }; -EXPORT_SYMBOL_GPL(fsl_mc_bus_dpdbg_type); static const struct device_type *fsl_mc_get_device_type(const char *type) { From d63cf1eea10c904c1b31b22ff3e118033ec7edfb Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Fri, 5 Dec 2025 13:13:32 +0000 Subject: [PATCH 146/297] comedi: don't use mutex for COMEDI_BUFINFO ioctl The main mutex in a comedi device can get held for quite a while when processing comedi instructions, so for performance reasons, the "read", "write", and "poll" file operations do not use it; they use the `attach_lock` rwsemaphore to protect against the comedi device becoming detached at an inopportune moment. As an alternative to using the "read" and "write" operations, user-space can mmap the data buffer and use the `COMEDI_BUFINFO` ioctl to manage data transfer through the buffer. However, the "ioctl" file handler currently locks the main mutex for all ioctl commands. Make the handling of the `COMEDI_BUFINFO` an exception, using the `attach_lock` rwsemaphore during normal operation. However, before it calls `do_become_nonbusy()` at the end of acquisition, it does need to lock the main mutex, but it needs to unlock the `attach_lock` rwsemaphore first to avoid deadlock. After locking the main mutex, it needs to check that it is still in a suitable state to become non-busy, because things may have changed while unlocked. Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20251205131332.16672-1-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/comedi_fops.c | 81 ++++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 13 deletions(-) diff --git a/drivers/comedi/comedi_fops.c b/drivers/comedi/comedi_fops.c index 657c98cd723e..3fea34e69052 100644 --- a/drivers/comedi/comedi_fops.c +++ b/drivers/comedi/comedi_fops.c @@ -1169,6 +1169,8 @@ static int do_chaninfo_ioctl(struct comedi_device *dev, * COMEDI_BUFINFO ioctl * buffer information * + * Note that the comedi device's mutex has not been locked for this ioctl. + * * arg: * pointer to comedi_bufinfo structure * @@ -1186,24 +1188,42 @@ static int do_bufinfo_ioctl(struct comedi_device *dev, struct comedi_async *async; unsigned int runflags; int retval = 0; + unsigned int old_detach_count; + unsigned int cmd_flags; bool become_nonbusy = false; + bool attach_locked; - lockdep_assert_held(&dev->mutex); if (copy_from_user(&bi, arg, sizeof(bi))) return -EFAULT; - if (bi.subdevice >= dev->n_subdevices) - return -EINVAL; + /* Protect against device detachment during operation. */ + down_read(&dev->attach_lock); + attach_locked = true; + old_detach_count = dev->detach_count; + + if (!dev->attached) { + dev_dbg(dev->class_dev, "no driver attached\n"); + retval = -ENODEV; + goto out; + } + + if (bi.subdevice >= dev->n_subdevices) { + retval = -EINVAL; + goto out; + } s = &dev->subdevices[bi.subdevice]; async = s->async; - if (!async || s->busy != file) - return -EINVAL; + if (!async || s->busy != file) { + retval = -EINVAL; + goto out; + } runflags = comedi_get_subdevice_runflags(s); - if (!(async->cmd.flags & CMDF_WRITE)) { + cmd_flags = async->cmd.flags; + if (!(cmd_flags & CMDF_WRITE)) { /* command was set up in "read" direction */ if (bi.bytes_read) { _comedi_buf_read_alloc(s, bi.bytes_read); @@ -1243,8 +1263,41 @@ static int do_bufinfo_ioctl(struct comedi_device *dev, bi.buf_read_count = async->buf_read_count; bi.buf_read_ptr = async->buf_read_ptr; - if (become_nonbusy) - do_become_nonbusy(dev, s); + if (become_nonbusy) { + struct comedi_subdevice *new_s = NULL; + + /* + * To avoid deadlock, cannot acquire dev->mutex + * while dev->attach_lock is held. + */ + up_read(&dev->attach_lock); + attach_locked = false; + mutex_lock(&dev->mutex); + /* + * Check device hasn't become detached behind our back. + * Checking dev->detach_count is unchanged ought to be + * sufficient, but check the subdevice pointer as well, + * and check the subdevice is still in a suitable state + * to become non-busy. It should still be "busy" after + * running an asynchronous commands, which should now have + * stopped, and for a command in the "read" direction, all + * available data should have been read. + */ + if (dev->attached && old_detach_count == dev->detach_count && + bi.subdevice < dev->n_subdevices) + new_s = &dev->subdevices[bi.subdevice]; + if (s == new_s && new_s->async == async && s->busy == file && + async->cmd.flags == cmd_flags && + !comedi_is_subdevice_running(s) && + ((cmd_flags & CMDF_WRITE) != 0 || + _comedi_buf_read_n_available(s) == 0)) + do_become_nonbusy(dev, s); + mutex_unlock(&dev->mutex); + } + +out: + if (attach_locked) + up_read(&dev->attach_lock); if (retval) return retval; @@ -2225,6 +2278,13 @@ static long comedi_unlocked_ioctl(struct file *file, unsigned int cmd, struct comedi_device *dev = cfp->dev; int rc; + /* Handle COMEDI_BUFINFO without locking the mutex first. */ + if (cmd == COMEDI_BUFINFO) { + return do_bufinfo_ioctl(dev, + (struct comedi_bufinfo __user *)arg, + file); + } + mutex_lock(&dev->mutex); /* @@ -2294,11 +2354,6 @@ static long comedi_unlocked_ioctl(struct file *file, unsigned int cmd, rc = do_rangeinfo_ioctl(dev, &it); break; } - case COMEDI_BUFINFO: - rc = do_bufinfo_ioctl(dev, - (struct comedi_bufinfo __user *)arg, - file); - break; case COMEDI_LOCK: rc = do_lock_ioctl(dev, arg, file); break; From b842f8c6397ab59786ad5d5bb8c6899d2fc55ae9 Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Wed, 10 Dec 2025 12:44:55 +0000 Subject: [PATCH 147/297] comedi: comedi_test: add a DIO subdevice The fake "comedi_test" device currently has two subdevices: an analog input subdevice, and an analog output subdevice. To make it a bit more useful for testing, add a third subdevice for digital I/O. The new DIO subdevice has 32 channels with each channel individually programmable as an input or an output. To add a bit of interaction, channels 0 to 15 are wired to channels 16 to 31 (0 to 16, 1 to 17, etc.), and the state of each wire can be read back on both of the channels connected to it. The outputs are modelled as NPN open collector outputs with a pull-up resistor on the wire, so the state of each wire (and the value read back from each channel connected to it) will be logic level 1 unless either channel is configured as an output at logic level 0. Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20251210124455.69131-1-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/comedi_test.c | 50 +++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/drivers/comedi/drivers/comedi_test.c b/drivers/comedi/drivers/comedi_test.c index 7984950f0f99..01aafce20ef8 100644 --- a/drivers/comedi/drivers/comedi_test.c +++ b/drivers/comedi/drivers/comedi_test.c @@ -692,6 +692,44 @@ static int waveform_ao_insn_config(struct comedi_device *dev, return -EINVAL; } +static int waveform_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + u32 driven_low; + u16 wires; + + /* Update the channel outputs. */ + comedi_dio_update_state(s, data); + /* + * We are modelling the outputs as NPN open collector (0 = driven low, + * 1 = high impedance), with the lower 16 channels wired to the upper + * 16 channels in pairs (0 to 16, 1 to 17, ..., 15 to 31), with a + * pull-up resistor on each wire. When reading back each channel, we + * read back the state of the wire to which it is connected. + * + * The state of each wire and the value read back from both channels + * connected to it will be logic 1 unless either channel connected to + * the wire is configured as an output in the logic 0 state. + */ + driven_low = s->io_bits & ~s->state; + wires = 0xFFFF & ~driven_low & ~(driven_low >> 16); + /* Read back the state of the wires for each pair of channels. */ + data[1] = wires | (wires << 16); + + return insn->n; +} + +static int waveform_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + /* configure each channel as input or output individually */ + return comedi_dio_insn_config(dev, s, insn, data, 0); +} + static int waveform_common_attach(struct comedi_device *dev, int amplitude, int period) { @@ -707,7 +745,7 @@ static int waveform_common_attach(struct comedi_device *dev, devpriv->wf_amplitude = amplitude; devpriv->wf_period = period; - ret = comedi_alloc_subdevices(dev, 2); + ret = comedi_alloc_subdevices(dev, 3); if (ret) return ret; @@ -746,6 +784,16 @@ static int waveform_common_attach(struct comedi_device *dev, for (i = 0; i < s->n_chan; i++) devpriv->ao_loopbacks[i] = s->maxdata / 2; + s = &dev->subdevices[2]; + /* digital input/output subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 32; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = waveform_dio_insn_bits; + s->insn_config = waveform_dio_insn_config; + devpriv->dev = dev; timer_setup(&devpriv->ai_timer, waveform_ai_timer, 0); timer_setup(&devpriv->ao_timer, waveform_ao_timer, 0); From 578d62a2e51614ea117ccf05fce6aaa6257dfd60 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 14 Dec 2025 12:27:27 -0800 Subject: [PATCH 148/297] comedi: comedi_8254: correct kernel-doc warnings Correct typos in 3 struct member descriptions to eliminate all kernel-doc warnings in comedi_8254.h: Warning: include/linux/comedi/comedi_8254.h:112 struct member 'next_div' not described in 'comedi_8254' Warning: include/linux/comedi/comedi_8254.h:112 struct member 'clock_src' not described in 'comedi_8254' Warning: include/linux/comedi/comedi_8254.h:112 struct member 'gate_src' not described in 'comedi_8254' Signed-off-by: Randy Dunlap Reviewed-by: Ian Abbott Link: https://patch.msgid.link/20251214202727.2215461-1-rdunlap@infradead.org Signed-off-by: Greg Kroah-Hartman --- include/linux/comedi/comedi_8254.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/linux/comedi/comedi_8254.h b/include/linux/comedi/comedi_8254.h index d527f04400df..91f5861061ec 100644 --- a/include/linux/comedi/comedi_8254.h +++ b/include/linux/comedi/comedi_8254.h @@ -83,11 +83,11 @@ typedef unsigned int comedi_8254_iocb_fn(struct comedi_8254 *i8254, int dir, * @divisor: divisor for single counter * @divisor1: divisor loaded into first cascaded counter * @divisor2: divisor loaded into second cascaded counter - * #next_div: next divisor for single counter + * @next_div: next divisor for single counter * @next_div1: next divisor to use for first cascaded counter * @next_div2: next divisor to use for second cascaded counter - * @clock_src; current clock source for each counter (driver specific) - * @gate_src; current gate source for each counter (driver specific) + * @clock_src: current clock source for each counter (driver specific) + * @gate_src: current gate source for each counter (driver specific) * @busy: flags used to indicate that a counter is "busy" * @insn_config: driver specific (*insn_config) callback */ From 38a5a54f6d8560d349d76a3fb9bc51499a9d0cda Mon Sep 17 00:00:00 2001 From: Can Peng Date: Mon, 8 Dec 2025 21:05:25 +0800 Subject: [PATCH 149/297] kgdbts: mark kgdbts_option_setup() with __init to free init memory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The kgdbts_option_setup() function is invoked only once early in boot via the __setup("kgdbts=", ...) mechanism to parse the kernel command-line option.After init is complete, it is never called again. Annotating it with __init places the function in the .init.text section, enabling the kernel to free its code memory during the init memory cleanup phase (free_initmem()). This reduces the kernel’s runtime memory footprint with no functional side effects. Signed-off-by: Can Peng Link: https://patch.msgid.link/20251208130525.2775885-1-pengcan@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/misc/kgdbts.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/kgdbts.c b/drivers/misc/kgdbts.c index 0cf31164b470..4a7022e263ed 100644 --- a/drivers/misc/kgdbts.c +++ b/drivers/misc/kgdbts.c @@ -1066,7 +1066,7 @@ static void kgdbts_run_tests(void) configured = 0; } -static int kgdbts_option_setup(char *opt) +static int __init kgdbts_option_setup(char *opt) { if (strlen(opt) >= MAX_CONFIG_LEN) { printk(KERN_ERR "kgdbts: config string too long\n"); From a2450bddb75f4c0da86ed02adbc9477e49edd311 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 14 Dec 2025 12:27:39 -0800 Subject: [PATCH 150/297] eeprom_93cx6: fix struct member kernel-doc Remove the function parameter parts of the struct member descriptions to prevent kernel-doc warnings: Warning: include/linux/eeprom_93cx6.h:64 struct member 'register_read' not described in 'eeprom_93cx6' Warning: ../include/linux/eeprom_93cx6.h:64 struct member 'register_write' not described in 'eeprom_93cx6' Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20251214202739.2216904-1-rdunlap@infradead.org Signed-off-by: Greg Kroah-Hartman --- include/linux/eeprom_93cx6.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/linux/eeprom_93cx6.h b/include/linux/eeprom_93cx6.h index 3a485cc0e0fa..194a4a49ff1d 100644 --- a/include/linux/eeprom_93cx6.h +++ b/include/linux/eeprom_93cx6.h @@ -31,10 +31,10 @@ * struct eeprom_93cx6 - control structure for setting the commands * for reading the eeprom data. * @data: private pointer for the driver. - * @register_read(struct eeprom_93cx6 *eeprom): handler to - * read the eeprom register, this function should set all reg_* fields. - * @register_write(struct eeprom_93cx6 *eeprom): handler to - * write to the eeprom register by using all reg_* fields. + * @register_read: handler to read the eeprom register; + * this function should set all reg_* fields. + * @register_write: handler to write to the eeprom register by using + * all reg_* fields. * @width: eeprom width, should be one of the PCI_EEPROM_WIDTH_* defines * @quirks: eeprom or controller quirks * @drive_data: Set if we're driving the data line. From bd87458c163820ad8f8c65bb24a0e35f145b8ace Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 14 Dec 2025 11:17:50 -0800 Subject: [PATCH 151/297] xilinx_hwicap: fifo_icap.c: fix all kernel-doc warnings Modify comments in fifo_icap.c to prevent all kernel-doc warnings: Warning: fifo_icap.c:51 This comment starts with '/**', but isn't a kernel-doc comment. * HwIcap Device Interrupt Status/Enable Registers Warning: fifo_icap.c:106 No description found for return value of 'fifo_icap_fifo_read' Warning: fifo_icap.c:160 No description found for return value of 'fifo_icap_get_status' Warning: fifo_icap.c:171 No description found for return value of 'fifo_icap_busy' Warning: fifo_icap.c:184 No description found for return value of 'fifo_icap_write_fifo_vacancy' Warning: fifo_icap.c:196 No description found for return value of 'fifo_icap_read_fifo_occupancy' Warning: fifo_icap.c:207 bad line: Warning: fifo_icap.c:214 No description found for return value of 'fifo_icap_set_configuration' Warning: fifo_icap.c:290 function parameter 'frame_buffer' not described in 'fifo_icap_get_configuration' Warning: fifo_icap.c:290 function parameter 'num_words' not described in 'fifo_icap_get_configuration' Warning: fifo_icap.c:290 No description found for return value of 'fifo_icap_get_configuration' Warning: fifo_icap.c:357 expecting prototype for buffer_icap_reset(). Prototype was for fifo_icap_reset() instead Signed-off-by: Randy Dunlap Reviewed-by: Michal Simek Link: https://patch.msgid.link/20251214191750.2173225-1-rdunlap@infradead.org Signed-off-by: Greg Kroah-Hartman --- drivers/char/xilinx_hwicap/fifo_icap.c | 27 ++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/drivers/char/xilinx_hwicap/fifo_icap.c b/drivers/char/xilinx_hwicap/fifo_icap.c index 619f3a30ec55..7bd786fa1be8 100644 --- a/drivers/char/xilinx_hwicap/fifo_icap.c +++ b/drivers/char/xilinx_hwicap/fifo_icap.c @@ -48,7 +48,7 @@ #define XHI_GIER_GIE_MASK 0x80000000 /* Global Interrupt enable Mask */ -/** +/* * HwIcap Device Interrupt Status/Enable Registers * * Interrupt Status Register (IPISR) : This register holds the @@ -102,6 +102,8 @@ static inline void fifo_icap_fifo_write(struct hwicap_drvdata *drvdata, * @drvdata: a pointer to the drvdata. * * This function will silently fail if the fifo is empty. + * + * Returns: 32-bit data from the Read FIFO **/ static inline u32 fifo_icap_fifo_read(struct hwicap_drvdata *drvdata) { @@ -156,6 +158,8 @@ static inline void fifo_icap_start_readback(struct hwicap_drvdata *drvdata) * D2 - Always 1 * D1 - Always 1 * D0 - Done bit + * + * Returns: the 32-bit ICAP status register **/ u32 fifo_icap_get_status(struct hwicap_drvdata *drvdata) { @@ -165,8 +169,11 @@ u32 fifo_icap_get_status(struct hwicap_drvdata *drvdata) } /** - * fifo_icap_busy - Return true if the ICAP is still processing a transaction. + * fifo_icap_busy - Check the ICAP busy status. * @drvdata: a pointer to the drvdata. + * + * Returns: %true if the ICAP is still processing a transaction, + * otherwise %false **/ static inline u32 fifo_icap_busy(struct hwicap_drvdata *drvdata) { @@ -178,7 +185,7 @@ static inline u32 fifo_icap_busy(struct hwicap_drvdata *drvdata) * fifo_icap_write_fifo_vacancy - Query the write fifo available space. * @drvdata: a pointer to the drvdata. * - * Return the number of words that can be safely pushed into the write fifo. + * Returns: the number of words that can be safely pushed into the write fifo. **/ static inline u32 fifo_icap_write_fifo_vacancy( struct hwicap_drvdata *drvdata) @@ -190,7 +197,7 @@ static inline u32 fifo_icap_write_fifo_vacancy( * fifo_icap_read_fifo_occupancy - Query the read fifo available data. * @drvdata: a pointer to the drvdata. * - * Return the number of words that can be safely read from the read fifo. + * Returns: the number of words that can be safely read from the read fifo. **/ static inline u32 fifo_icap_read_fifo_occupancy( struct hwicap_drvdata *drvdata) @@ -205,10 +212,12 @@ static inline u32 fifo_icap_read_fifo_occupancy( * ICAP device. * @num_words: the number of words (32 bit) to write to the ICAP * device. - + * * This function writes the given user data to the Write FIFO in * polled mode and starts the transfer of the data to * the ICAP device. + * + * Returns: %0 on success or %-errno on failure. **/ int fifo_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *frame_buffer, u32 num_words) @@ -280,11 +289,13 @@ int fifo_icap_set_configuration(struct hwicap_drvdata *drvdata, /** * fifo_icap_get_configuration - Read configuration data from the device. * @drvdata: a pointer to the drvdata. - * @data: Address of the data representing the partial bitstream - * @size: the size of the partial bitstream in 32 bit words. + * @frame_buffer: Address of the data representing the partial bitstream + * @num_words: the size of the partial bitstream in 32 bit words. * * This function reads the specified number of words from the ICAP device in * the polled mode. + * + * Returns: %0 on success or %-errno on failure. */ int fifo_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *frame_buffer, u32 num_words) @@ -347,7 +358,7 @@ int fifo_icap_get_configuration(struct hwicap_drvdata *drvdata, } /** - * buffer_icap_reset - Reset the logic of the icap device. + * fifo_icap_reset - Reset the logic of the icap device. * @drvdata: a pointer to the drvdata. * * This function forces the software reset of the complete HWICAP device. From 86b31a2c81817d09cb5cbea9457a9f294ed9bb7d Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 17 Dec 2025 13:36:52 +0100 Subject: [PATCH 152/297] test_list_sort: fix up const mismatch In the internal cmp function, a const pointer is cast out to a non-const pointer by using container_of(). This is probably not what is intended at all, so fix up the const marking to properly preserve what is really happening (i.e. the const should flow through the container_of() call) Cc: Jakub Kicinski Cc: David Gow Cc: Masami Hiramatsu (Google) Cc: Vlastimil Babka Cc: Kees Cook Cc: linux-kernel@vger.kernel.org Reviewed-by: David Gow Link: https://patch.msgid.link/2025121751-backtrack-manifesto-7c57@gregkh Signed-off-by: Greg Kroah-Hartman --- lib/tests/test_list_sort.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tests/test_list_sort.c b/lib/tests/test_list_sort.c index 30879abc8a42..28158557b164 100644 --- a/lib/tests/test_list_sort.c +++ b/lib/tests/test_list_sort.c @@ -26,7 +26,7 @@ struct debug_el { unsigned int serial; }; -static void check(struct kunit *test, struct debug_el *ela, struct debug_el *elb) +static void check(struct kunit *test, const struct debug_el *ela, const struct debug_el *elb) { struct debug_el **elts = test->priv; @@ -46,7 +46,7 @@ static void check(struct kunit *test, struct debug_el *ela, struct debug_el *elb /* `priv` is the test pointer so check() can fail the test if the list is invalid. */ static int cmp(void *priv, const struct list_head *a, const struct list_head *b) { - struct debug_el *ela, *elb; + const struct debug_el *ela, *elb; ela = container_of(a, struct debug_el, list); elb = container_of(b, struct debug_el, list); From 68a9459a5c4e8b978c3eb5d75be5446799e483de Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 17 Dec 2025 13:32:47 +0100 Subject: [PATCH 153/297] kunit: fix up const mis-match in many assert functions In many kunit assert functions a const pointer is passed to container_of() and out pops a non-const pointer, which really isn't the correct thing to do at all. Fix this up by correctly marking the casted-to pointer as const to preserve the marking. Cc: Brendan Higgins Cc: David Gow Cc: Rae Moar Cc: linux-kselftest@vger.kernel.org Cc: kunit-dev@googlegroups.com Reviewed-by: David Gow Link: https://patch.msgid.link/2025121746-result-staleness-5a68@gregkh Signed-off-by: Greg Kroah-Hartman --- lib/kunit/assert.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/kunit/assert.c b/lib/kunit/assert.c index 867aa5c4bccf..4c751ad8506a 100644 --- a/lib/kunit/assert.c +++ b/lib/kunit/assert.c @@ -51,7 +51,7 @@ void kunit_unary_assert_format(const struct kunit_assert *assert, const struct va_format *message, struct string_stream *stream) { - struct kunit_unary_assert *unary_assert; + const struct kunit_unary_assert *unary_assert; unary_assert = container_of(assert, struct kunit_unary_assert, assert); @@ -71,7 +71,7 @@ void kunit_ptr_not_err_assert_format(const struct kunit_assert *assert, const struct va_format *message, struct string_stream *stream) { - struct kunit_ptr_not_err_assert *ptr_assert; + const struct kunit_ptr_not_err_assert *ptr_assert; ptr_assert = container_of(assert, struct kunit_ptr_not_err_assert, assert); @@ -117,7 +117,7 @@ void kunit_binary_assert_format(const struct kunit_assert *assert, const struct va_format *message, struct string_stream *stream) { - struct kunit_binary_assert *binary_assert; + const struct kunit_binary_assert *binary_assert; binary_assert = container_of(assert, struct kunit_binary_assert, assert); @@ -145,7 +145,7 @@ void kunit_binary_ptr_assert_format(const struct kunit_assert *assert, const struct va_format *message, struct string_stream *stream) { - struct kunit_binary_ptr_assert *binary_assert; + const struct kunit_binary_ptr_assert *binary_assert; binary_assert = container_of(assert, struct kunit_binary_ptr_assert, assert); @@ -185,7 +185,7 @@ void kunit_binary_str_assert_format(const struct kunit_assert *assert, const struct va_format *message, struct string_stream *stream) { - struct kunit_binary_str_assert *binary_assert; + const struct kunit_binary_str_assert *binary_assert; binary_assert = container_of(assert, struct kunit_binary_str_assert, assert); @@ -237,7 +237,7 @@ void kunit_mem_assert_format(const struct kunit_assert *assert, const struct va_format *message, struct string_stream *stream) { - struct kunit_mem_assert *mem_assert; + const struct kunit_mem_assert *mem_assert; mem_assert = container_of(assert, struct kunit_mem_assert, assert); From ed1613fc18834b5ec38d3534e96e4bc990289aa2 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 28 Dec 2025 11:05:02 -0800 Subject: [PATCH 154/297] stm class: Kconfig: correct symbol name Drop the leading "CONFIG_" when referring to Kconfig symbols-- it is supplied by the kconfig software. This make the default values work as (apparently) expected. Fixes: a02509f301c6 ("stm class: Factor out default framing protocol") Fixes: d69d5e83110f ("stm class: Add MIPI SyS-T protocol support") Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20251228190502.2480758-1-rdunlap@infradead.org Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/stm/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwtracing/stm/Kconfig b/drivers/hwtracing/stm/Kconfig index eda6b11d40a1..cd7f0b0f3fbe 100644 --- a/drivers/hwtracing/stm/Kconfig +++ b/drivers/hwtracing/stm/Kconfig @@ -13,7 +13,7 @@ if STM config STM_PROTO_BASIC tristate "Basic STM framing protocol driver" - default CONFIG_STM + default STM help This is a simple framing protocol for sending data over STM devices. This was the protocol that the STM framework used @@ -28,7 +28,7 @@ config STM_PROTO_BASIC config STM_PROTO_SYS_T tristate "MIPI SyS-T STM framing protocol driver" - default CONFIG_STM + default STM help This is an implementation of MIPI SyS-T protocol to be used over the STP transport. In addition to the data payload, it From 5f0bf80cc5e04d31eeb201683e0b477c24bd18e7 Mon Sep 17 00:00:00 2001 From: Matthew Schwartz Date: Sat, 3 Jan 2026 12:42:26 -0800 Subject: [PATCH 155/297] mmc: rtsx_pci: add quirk to disable MMC_CAP_AGGRESSIVE_PM for RTS525A Using MMC_CAP_AGGRESSIVE_PM on RTS525A card readers causes game performance issues when the card reader comes back from idle into active use. This can be observed in Hades II when loading new sections of the game or menu after the card reader puts itself into idle, and presents as a 1-2 second hang. Add EXTRA_CAPS_NO_AGGRESSIVE_PM quirk to allow cardreader drivers to opt-out of aggressive PM, and set it for RTS525A. Closes: https://lore.kernel.org/linux-mmc/ff9a7c20-f465-4afa-bf29-708d4a52974a@linux.dev/ Signed-off-by: Matthew Schwartz Link: https://patch.msgid.link/20260103204226.71752-1-matthew.schwartz@linux.dev Signed-off-by: Greg Kroah-Hartman --- drivers/misc/cardreader/rts5249.c | 3 +++ drivers/mmc/host/rtsx_pci_sdmmc.c | 4 ++-- include/linux/rtsx_pci.h | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/misc/cardreader/rts5249.c b/drivers/misc/cardreader/rts5249.c index 38aefd8db452..87d576a03e68 100644 --- a/drivers/misc/cardreader/rts5249.c +++ b/drivers/misc/cardreader/rts5249.c @@ -78,6 +78,9 @@ static void rtsx_base_fetch_vendor_settings(struct rtsx_pcr *pcr) if (CHK_PCI_PID(pcr, PID_524A) || CHK_PCI_PID(pcr, PID_525A)) pcr->rtd3_en = rtsx_reg_to_rtd3_uhsii(reg); + if (CHK_PCI_PID(pcr, PID_525A)) + pcr->extra_caps |= EXTRA_CAPS_NO_AGGRESSIVE_PM; + if (rtsx_check_mmc_support(reg)) pcr->extra_caps |= EXTRA_CAPS_NO_MMC; pcr->sd30_drive_sel_3v3 = rtsx_reg_to_sd30_drive_sel_3v3(reg); diff --git a/drivers/mmc/host/rtsx_pci_sdmmc.c b/drivers/mmc/host/rtsx_pci_sdmmc.c index dc2587ff8519..5d3599ee06bf 100644 --- a/drivers/mmc/host/rtsx_pci_sdmmc.c +++ b/drivers/mmc/host/rtsx_pci_sdmmc.c @@ -1456,8 +1456,8 @@ static void realtek_init_host(struct realtek_pci_sdmmc *host) mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED | MMC_CAP_BUS_WIDTH_TEST | MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25; - if (pcr->rtd3_en) - mmc->caps = mmc->caps | MMC_CAP_AGGRESSIVE_PM; + if (pcr->rtd3_en && !(pcr->extra_caps & EXTRA_CAPS_NO_AGGRESSIVE_PM)) + mmc->caps |= MMC_CAP_AGGRESSIVE_PM; mmc->caps2 = MMC_CAP2_NO_PRESCAN_POWERUP | MMC_CAP2_FULL_PWR_CYCLE | MMC_CAP2_NO_SDIO; mmc->max_current_330 = 400; diff --git a/include/linux/rtsx_pci.h b/include/linux/rtsx_pci.h index 3c5689356004..f6122349c00e 100644 --- a/include/linux/rtsx_pci.h +++ b/include/linux/rtsx_pci.h @@ -1230,6 +1230,7 @@ struct rtsx_pcr { #define EXTRA_CAPS_MMC_8BIT (1 << 5) #define EXTRA_CAPS_NO_MMC (1 << 7) #define EXTRA_CAPS_SD_EXPRESS (1 << 8) +#define EXTRA_CAPS_NO_AGGRESSIVE_PM (1 << 9) u32 extra_caps; #define IC_VER_A 0 From eac85fbd0867c25ac517f58fae401d65c627edff Mon Sep 17 00:00:00 2001 From: Matthew Schwartz Date: Sun, 4 Jan 2026 22:02:35 -0800 Subject: [PATCH 156/297] mmc: rtsx: reset power state on suspend When rtsx_pci suspends, the card reader hardware powers off but the sdmmc driver's prev_power_state remains as MMC_POWER_ON. This causes sd_power_on to skip reinitialization on the next I/O request, leading to DMA transfer timeouts and errors on resume 20% of the time. Add a power_off slot callback so the PCR can notify the sdmmc driver during suspend. The sdmmc driver resets prev_power_state, and sd_request checks this to reinitialize the card before the next I/O. Signed-off-by: Matthew Schwartz Link: https://patch.msgid.link/20260105060236.400366-2-matthew.schwartz@linux.dev Signed-off-by: Greg Kroah-Hartman --- drivers/misc/cardreader/rtsx_pcr.c | 9 +++++++++ drivers/mmc/host/rtsx_pci_sdmmc.c | 22 ++++++++++++++++++++++ include/linux/rtsx_common.h | 1 + 3 files changed, 32 insertions(+) diff --git a/drivers/misc/cardreader/rtsx_pcr.c b/drivers/misc/cardreader/rtsx_pcr.c index f9952d76d6ed..f1f4d8ed544d 100644 --- a/drivers/misc/cardreader/rtsx_pcr.c +++ b/drivers/misc/cardreader/rtsx_pcr.c @@ -1654,6 +1654,7 @@ static int __maybe_unused rtsx_pci_suspend(struct device *dev_d) struct pci_dev *pcidev = to_pci_dev(dev_d); struct pcr_handle *handle = pci_get_drvdata(pcidev); struct rtsx_pcr *pcr = handle->pcr; + struct rtsx_slot *slot = &pcr->slots[RTSX_SD_CARD]; dev_dbg(&(pcidev->dev), "--> %s\n", __func__); @@ -1661,6 +1662,9 @@ static int __maybe_unused rtsx_pci_suspend(struct device *dev_d) mutex_lock(&pcr->pcr_mutex); + if (slot->p_dev && slot->power_off) + slot->power_off(slot->p_dev); + rtsx_pci_power_off(pcr, HOST_ENTER_S3, false); mutex_unlock(&pcr->pcr_mutex); @@ -1772,12 +1776,17 @@ static int rtsx_pci_runtime_suspend(struct device *device) struct pci_dev *pcidev = to_pci_dev(device); struct pcr_handle *handle = pci_get_drvdata(pcidev); struct rtsx_pcr *pcr = handle->pcr; + struct rtsx_slot *slot = &pcr->slots[RTSX_SD_CARD]; dev_dbg(device, "--> %s\n", __func__); cancel_delayed_work_sync(&pcr->carddet_work); mutex_lock(&pcr->pcr_mutex); + + if (slot->p_dev && slot->power_off) + slot->power_off(slot->p_dev); + rtsx_pci_power_off(pcr, HOST_ENTER_S3, true); mutex_unlock(&pcr->pcr_mutex); diff --git a/drivers/mmc/host/rtsx_pci_sdmmc.c b/drivers/mmc/host/rtsx_pci_sdmmc.c index 5d3599ee06bf..b847d79d4d8c 100644 --- a/drivers/mmc/host/rtsx_pci_sdmmc.c +++ b/drivers/mmc/host/rtsx_pci_sdmmc.c @@ -47,6 +47,7 @@ struct realtek_pci_sdmmc { }; static int sdmmc_init_sd_express(struct mmc_host *mmc, struct mmc_ios *ios); +static int sd_power_on(struct realtek_pci_sdmmc *host, unsigned char power_mode); static inline struct device *sdmmc_dev(struct realtek_pci_sdmmc *host) { @@ -821,6 +822,15 @@ static void sd_request(struct work_struct *work) rtsx_pci_start_run(pcr); + if (host->prev_power_state == MMC_POWER_OFF) { + err = sd_power_on(host, MMC_POWER_ON); + if (err) { + cmd->error = err; + mutex_unlock(&pcr->pcr_mutex); + goto finish; + } + } + rtsx_pci_switch_clock(pcr, host->clock, host->ssc_depth, host->initial_mode, host->double_clk, host->vpclk); rtsx_pci_write_register(pcr, CARD_SELECT, 0x07, SD_MOD_SEL); @@ -1481,6 +1491,16 @@ static void rtsx_pci_sdmmc_card_event(struct platform_device *pdev) mmc_detect_change(host->mmc, 0); } +static void rtsx_pci_sdmmc_power_off(struct platform_device *pdev) +{ + struct realtek_pci_sdmmc *host = platform_get_drvdata(pdev); + + if (!host) + return; + + host->prev_power_state = MMC_POWER_OFF; +} + static int rtsx_pci_sdmmc_drv_probe(struct platform_device *pdev) { struct mmc_host *mmc; @@ -1513,6 +1533,7 @@ static int rtsx_pci_sdmmc_drv_probe(struct platform_device *pdev) platform_set_drvdata(pdev, host); pcr->slots[RTSX_SD_CARD].p_dev = pdev; pcr->slots[RTSX_SD_CARD].card_event = rtsx_pci_sdmmc_card_event; + pcr->slots[RTSX_SD_CARD].power_off = rtsx_pci_sdmmc_power_off; mutex_init(&host->host_mutex); @@ -1544,6 +1565,7 @@ static void rtsx_pci_sdmmc_drv_remove(struct platform_device *pdev) pcr = host->pcr; pcr->slots[RTSX_SD_CARD].p_dev = NULL; pcr->slots[RTSX_SD_CARD].card_event = NULL; + pcr->slots[RTSX_SD_CARD].power_off = NULL; mmc = host->mmc; cancel_work_sync(&host->work); diff --git a/include/linux/rtsx_common.h b/include/linux/rtsx_common.h index da9c8c6b5d50..f294f478f0c0 100644 --- a/include/linux/rtsx_common.h +++ b/include/linux/rtsx_common.h @@ -32,6 +32,7 @@ struct platform_device; struct rtsx_slot { struct platform_device *p_dev; void (*card_event)(struct platform_device *p_dev); + void (*power_off)(struct platform_device *p_dev); }; #endif From aced969e9bf3701dc75cfca57c78c031b7875b9d Mon Sep 17 00:00:00 2001 From: Matthew Schwartz Date: Sun, 4 Jan 2026 22:02:36 -0800 Subject: [PATCH 157/297] mmc: rtsx_pci_sdmmc: increase power-on settling delay to 5ms The existing 1ms delay in sd_power_on is insufficient and causes resume errors around 4% of the time. Increasing the delay to 5ms resolves this issue after testing 300 s2idle cycles. Fixes: 1f311c94aabd ("mmc: rtsx: add 74 Clocks in power on flow") Signed-off-by: Matthew Schwartz Link: https://patch.msgid.link/20260105060236.400366-3-matthew.schwartz@linux.dev Signed-off-by: Greg Kroah-Hartman --- drivers/mmc/host/rtsx_pci_sdmmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/rtsx_pci_sdmmc.c b/drivers/mmc/host/rtsx_pci_sdmmc.c index b847d79d4d8c..767816c09491 100644 --- a/drivers/mmc/host/rtsx_pci_sdmmc.c +++ b/drivers/mmc/host/rtsx_pci_sdmmc.c @@ -947,7 +947,7 @@ static int sd_power_on(struct realtek_pci_sdmmc *host, unsigned char power_mode) if (err < 0) return err; - mdelay(1); + mdelay(5); err = rtsx_pci_write_register(pcr, CARD_OE, SD_OUTPUT_EN, SD_OUTPUT_EN); if (err < 0) From 1e0ac56c92e26115cbc8cfc639843725cb3a7d6a Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Wed, 14 Jan 2026 18:55:30 +0200 Subject: [PATCH 158/297] iio: pressure: mprls0025pa: fix spi_transfer struct initialisation Make sure that the spi_transfer struct is zeroed out before use. Fixes: a0858f0cd28e ("iio: pressure: mprls0025pa add SPI driver") Signed-off-by: Petre Rodan Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/mprls0025pa_spi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/pressure/mprls0025pa_spi.c b/drivers/iio/pressure/mprls0025pa_spi.c index d04102f8a4a0..e6bb75de3411 100644 --- a/drivers/iio/pressure/mprls0025pa_spi.c +++ b/drivers/iio/pressure/mprls0025pa_spi.c @@ -40,7 +40,7 @@ static int mpr_spi_xfer(struct mpr_data *data, const u8 cmd, const u8 pkt_len) { struct spi_device *spi = to_spi_device(data->dev); struct mpr_spi_buf *buf = spi_get_drvdata(spi); - struct spi_transfer xfer; + struct spi_transfer xfer = { }; if (pkt_len > MPR_MEASUREMENT_RD_SIZE) return -EOVERFLOW; From 583fa86ca581595b1f534a8de6d49ba8b3bf7196 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Wed, 14 Jan 2026 18:55:31 +0200 Subject: [PATCH 159/297] iio: pressure: mprls0025pa: fix SPI CS delay violation Based on the sensor datasheet in chapter 7.6 SPI timing, Table 20, during the SPI transfer there is a minimum time interval requirement between the CS being asserted and the first clock edge (tHDSS). This minimum interval of 2.5us is being violated if two consecutive SPI transfers are queued up. Fixes: a0858f0cd28e ("iio: pressure: mprls0025pa add SPI driver") Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/micropressure-mpr-series/documents/sps-siot-mpr-series-datasheet-32332628-ciid-172626.pdf?download=false Signed-off-by: Petre Rodan Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/mprls0025pa_spi.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/iio/pressure/mprls0025pa_spi.c b/drivers/iio/pressure/mprls0025pa_spi.c index e6bb75de3411..cf17eb2e7208 100644 --- a/drivers/iio/pressure/mprls0025pa_spi.c +++ b/drivers/iio/pressure/mprls0025pa_spi.c @@ -8,6 +8,7 @@ * https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/micropressure-mpr-series/documents/sps-siot-mpr-series-datasheet-32332628-ciid-172626.pdf */ +#include #include #include #include @@ -40,17 +41,25 @@ static int mpr_spi_xfer(struct mpr_data *data, const u8 cmd, const u8 pkt_len) { struct spi_device *spi = to_spi_device(data->dev); struct mpr_spi_buf *buf = spi_get_drvdata(spi); - struct spi_transfer xfer = { }; + struct spi_transfer xfers[2] = { }; if (pkt_len > MPR_MEASUREMENT_RD_SIZE) return -EOVERFLOW; buf->tx[0] = cmd; - xfer.tx_buf = buf->tx; - xfer.rx_buf = data->buffer; - xfer.len = pkt_len; - return spi_sync_transfer(spi, &xfer, 1); + /* + * Dummy transfer with no data, just cause a 2.5us+ delay between the CS assert + * and the first clock edge as per the datasheet tHDSS timing requirement. + */ + xfers[0].delay.value = 2500; + xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; + + xfers[1].tx_buf = buf->tx; + xfers[1].rx_buf = data->buffer; + xfers[1].len = pkt_len; + + return spi_sync_transfer(spi, xfers, ARRAY_SIZE(xfers)); } static const struct mpr_ops mpr_spi_ops = { From fff3f1a7d805684e4701a70bfaeba39622b59dbc Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Wed, 14 Jan 2026 18:55:32 +0200 Subject: [PATCH 160/297] iio: pressure: mprls0025pa: fix interrupt flag Interrupt falling/rising flags should only be defined in the device tree. Fixes: 713337d9143e ("iio: pressure: Honeywell mprls0025pa pressure sensor") Signed-off-by: Petre Rodan Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/mprls0025pa.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/iio/pressure/mprls0025pa.c b/drivers/iio/pressure/mprls0025pa.c index 2336f2760eae..4b23f87a822b 100644 --- a/drivers/iio/pressure/mprls0025pa.c +++ b/drivers/iio/pressure/mprls0025pa.c @@ -418,10 +418,8 @@ int mpr_common_probe(struct device *dev, const struct mpr_ops *ops, int irq) data->offset = div_s64_rem(offset, NANO, &data->offset2); if (data->irq > 0) { - ret = devm_request_irq(dev, data->irq, mpr_eoc_handler, - IRQF_TRIGGER_RISING, - dev_name(dev), - data); + ret = devm_request_irq(dev, data->irq, mpr_eoc_handler, 0, + dev_name(dev), data); if (ret) return dev_err_probe(dev, ret, "request irq %d failed\n", data->irq); From 8a228e036926f7e57421d750c3724e63f11b808a Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Wed, 14 Jan 2026 18:55:33 +0200 Subject: [PATCH 161/297] iio: pressure: mprls0025pa: fix scan_type struct Fix the scan_type sign and realbits assignment. The pressure is a 24bit unsigned int between output_min and output_max. transfer function A: 10% to 90% of 2^24 transfer function B: 2.5% to 22.5% of 2^24 transfer function C: 20% to 80% of 2^24 [MPR_FUNCTION_A] = { .output_min = 1677722, .output_max = 15099494 } [MPR_FUNCTION_B] = { .output_min = 419430, .output_max = 3774874 } [MPR_FUNCTION_C] = { .output_min = 3355443, .output_max = 13421773 } Fixes: 713337d9143e ("iio: pressure: Honeywell mprls0025pa pressure sensor") Signed-off-by: Petre Rodan Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/mprls0025pa.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/pressure/mprls0025pa.c b/drivers/iio/pressure/mprls0025pa.c index 4b23f87a822b..6ba45d4c16b3 100644 --- a/drivers/iio/pressure/mprls0025pa.c +++ b/drivers/iio/pressure/mprls0025pa.c @@ -160,8 +160,8 @@ static const struct iio_chan_spec mpr_channels[] = { BIT(IIO_CHAN_INFO_OFFSET), .scan_index = 0, .scan_type = { - .sign = 's', - .realbits = 32, + .sign = 'u', + .realbits = 24, .storagebits = 32, .endianness = IIO_CPU, }, From d63403d4e31ae537fefc5c0ee9d90f29b4fc532b Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Wed, 14 Jan 2026 18:55:34 +0200 Subject: [PATCH 162/297] iio: pressure: mprls0025pa: fix pressure calculation A sign change is needed for proper calculation of the pressure. This is a minor fix since it only affects users that might have custom silicon from Honeywell that has honeywell,pmin-pascal != 0. Also due to the fact that raw pressure values can not be lower than output_min (400k-3.3M) there is no need to calculate a decimal for the offset. Fixes: 713337d9143e ("iio: pressure: Honeywell mprls0025pa pressure sensor") Signed-off-by: Petre Rodan Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/mprls0025pa.c | 26 +++++++++++--------------- drivers/iio/pressure/mprls0025pa.h | 2 -- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/drivers/iio/pressure/mprls0025pa.c b/drivers/iio/pressure/mprls0025pa.c index 6ba45d4c16b3..d4133fef91fa 100644 --- a/drivers/iio/pressure/mprls0025pa.c +++ b/drivers/iio/pressure/mprls0025pa.c @@ -59,7 +59,7 @@ * * Values given to the userspace in sysfs interface: * * raw - press_cnt - * * offset - (-1 * outputmin) - pmin / scale + * * offset - (-1 * outputmin) + pmin / scale * note: With all sensors from the datasheet pmin = 0 * which reduces the offset to (-1 * outputmin) */ @@ -313,8 +313,7 @@ static int mpr_read_raw(struct iio_dev *indio_dev, return IIO_VAL_INT_PLUS_NANO; case IIO_CHAN_INFO_OFFSET: *val = data->offset; - *val2 = data->offset2; - return IIO_VAL_INT_PLUS_NANO; + return IIO_VAL_INT; default: return -EINVAL; } @@ -330,8 +329,9 @@ int mpr_common_probe(struct device *dev, const struct mpr_ops *ops, int irq) struct mpr_data *data; struct iio_dev *indio_dev; const char *triplet; - s64 scale, offset; + s64 odelta, pdelta; u32 func; + s32 tmp; indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) @@ -405,17 +405,13 @@ int mpr_common_probe(struct device *dev, const struct mpr_ops *ops, int irq) data->outmin = mpr_func_spec[data->function].output_min; data->outmax = mpr_func_spec[data->function].output_max; - /* use 64 bit calculation for preserving a reasonable precision */ - scale = div_s64(((s64)(data->pmax - data->pmin)) * NANO, - data->outmax - data->outmin); - data->scale = div_s64_rem(scale, NANO, &data->scale2); - /* - * multiply with NANO before dividing by scale and later divide by NANO - * again. - */ - offset = ((-1LL) * (s64)data->outmin) * NANO - - div_s64(div_s64((s64)data->pmin * NANO, scale), NANO); - data->offset = div_s64_rem(offset, NANO, &data->offset2); + odelta = data->outmax - data->outmin; + pdelta = data->pmax - data->pmin; + + data->scale = div_s64_rem(div_s64(pdelta * NANO, odelta), NANO, &tmp); + data->scale2 = tmp; + + data->offset = div_s64(odelta * data->pmin, pdelta) - data->outmin; if (data->irq > 0) { ret = devm_request_irq(dev, data->irq, mpr_eoc_handler, 0, diff --git a/drivers/iio/pressure/mprls0025pa.h b/drivers/iio/pressure/mprls0025pa.h index d62a018eaff3..b6944b305126 100644 --- a/drivers/iio/pressure/mprls0025pa.h +++ b/drivers/iio/pressure/mprls0025pa.h @@ -53,7 +53,6 @@ enum mpr_func_id { * @scale: pressure scale * @scale2: pressure scale, decimal number * @offset: pressure offset - * @offset2: pressure offset, decimal number * @gpiod_reset: reset * @irq: end of conversion irq. used to distinguish between irq mode and * reading in a loop until data is ready @@ -75,7 +74,6 @@ struct mpr_data { int scale; int scale2; int offset; - int offset2; struct gpio_desc *gpiod_reset; int irq; struct completion completion; From 448889d3abbba5a6444e21fe3bd64e115d147020 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Wed, 14 Jan 2026 18:55:35 +0200 Subject: [PATCH 163/297] iio: pressure: mprls0025pa: cleanup includes Remove unused headers and add required headers as needed. Signed-off-by: Petre Rodan Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/mprls0025pa.c | 7 +++++++ drivers/iio/pressure/mprls0025pa.h | 3 --- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/iio/pressure/mprls0025pa.c b/drivers/iio/pressure/mprls0025pa.c index d4133fef91fa..3a5dbec81b50 100644 --- a/drivers/iio/pressure/mprls0025pa.c +++ b/drivers/iio/pressure/mprls0025pa.c @@ -12,6 +12,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include #include #include @@ -21,6 +27,7 @@ #include #include +#include #include #include diff --git a/drivers/iio/pressure/mprls0025pa.h b/drivers/iio/pressure/mprls0025pa.h index b6944b305126..eab877da3451 100644 --- a/drivers/iio/pressure/mprls0025pa.h +++ b/drivers/iio/pressure/mprls0025pa.h @@ -12,10 +12,7 @@ #define _MPRLS0025PA_H #include -#include -#include #include -#include #include #include From 77261502ea5c87498f10bbb763b5b8a64fb5d7e5 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Wed, 14 Jan 2026 18:55:36 +0200 Subject: [PATCH 164/297] iio: pressure: mprls0025pa: remove redundant declarations Remove the iio_chan_spec and iio_dev structs which are already defined in the included iio.h header. Signed-off-by: Petre Rodan Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/mprls0025pa.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/iio/pressure/mprls0025pa.h b/drivers/iio/pressure/mprls0025pa.h index eab877da3451..e34253af8e23 100644 --- a/drivers/iio/pressure/mprls0025pa.h +++ b/drivers/iio/pressure/mprls0025pa.h @@ -25,9 +25,6 @@ struct device; -struct iio_chan_spec; -struct iio_dev; - struct mpr_data; struct mpr_ops; From 7071f9f06210c6c828a5db205fa18dfd187ba529 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Wed, 14 Jan 2026 18:55:37 +0200 Subject: [PATCH 165/297] iio: pressure: mprls0025pa: rename buffer variable For the reason of better naming consistency rename priv->buffer into priv->rx_buf since tx_buf will get introduced in the next patch. Signed-off-by: Petre Rodan Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/mprls0025pa.c | 10 +++++----- drivers/iio/pressure/mprls0025pa.h | 4 ++-- drivers/iio/pressure/mprls0025pa_i2c.c | 4 ++-- drivers/iio/pressure/mprls0025pa_spi.c | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/iio/pressure/mprls0025pa.c b/drivers/iio/pressure/mprls0025pa.c index 3a5dbec81b50..245d7ed82b6b 100644 --- a/drivers/iio/pressure/mprls0025pa.c +++ b/drivers/iio/pressure/mprls0025pa.c @@ -238,7 +238,7 @@ static int mpr_read_pressure(struct mpr_data *data, s32 *press) ret); return ret; } - if (!(data->buffer[0] & MPR_ST_ERR_FLAG)) + if (!(data->rx_buf[0] & MPR_ST_ERR_FLAG)) break; } if (i == nloops) { @@ -251,15 +251,15 @@ static int mpr_read_pressure(struct mpr_data *data, s32 *press) if (ret < 0) return ret; - if (data->buffer[0] & MPR_ST_ERR_FLAG) { + if (data->rx_buf[0] & MPR_ST_ERR_FLAG) { dev_err(data->dev, - "unexpected status byte %02x\n", data->buffer[0]); + "unexpected status byte %02x\n", data->rx_buf[0]); return -ETIMEDOUT; } - *press = get_unaligned_be24(&data->buffer[1]); + *press = get_unaligned_be24(&data->rx_buf[1]); - dev_dbg(dev, "received: %*ph cnt: %d\n", ret, data->buffer, *press); + dev_dbg(dev, "received: %*ph cnt: %d\n", ret, data->rx_buf, *press); return 0; } diff --git a/drivers/iio/pressure/mprls0025pa.h b/drivers/iio/pressure/mprls0025pa.h index e34253af8e23..119ebb0ba567 100644 --- a/drivers/iio/pressure/mprls0025pa.h +++ b/drivers/iio/pressure/mprls0025pa.h @@ -54,7 +54,7 @@ enum mpr_func_id { * @chan: channel values for buffered mode * @chan.pres: pressure value * @chan.ts: timestamp - * @buffer: raw conversion data + * @rx_buf: raw conversion data */ struct mpr_data { struct device *dev; @@ -75,7 +75,7 @@ struct mpr_data { s32 pres; aligned_s64 ts; } chan; - u8 buffer[MPR_MEASUREMENT_RD_SIZE] __aligned(IIO_DMA_MINALIGN); + u8 rx_buf[MPR_MEASUREMENT_RD_SIZE] __aligned(IIO_DMA_MINALIGN); }; struct mpr_ops { diff --git a/drivers/iio/pressure/mprls0025pa_i2c.c b/drivers/iio/pressure/mprls0025pa_i2c.c index 79811fd4a02b..36da0059b19f 100644 --- a/drivers/iio/pressure/mprls0025pa_i2c.c +++ b/drivers/iio/pressure/mprls0025pa_i2c.c @@ -30,8 +30,8 @@ static int mpr_i2c_read(struct mpr_data *data, const u8 unused, const u8 cnt) if (cnt > MPR_MEASUREMENT_RD_SIZE) return -EOVERFLOW; - memset(data->buffer, 0, MPR_MEASUREMENT_RD_SIZE); - ret = i2c_master_recv(client, data->buffer, cnt); + memset(data->rx_buf, 0, MPR_MEASUREMENT_RD_SIZE); + ret = i2c_master_recv(client, data->rx_buf, cnt); if (ret < 0) return ret; else if (ret != cnt) diff --git a/drivers/iio/pressure/mprls0025pa_spi.c b/drivers/iio/pressure/mprls0025pa_spi.c index cf17eb2e7208..247b65226bb9 100644 --- a/drivers/iio/pressure/mprls0025pa_spi.c +++ b/drivers/iio/pressure/mprls0025pa_spi.c @@ -56,7 +56,7 @@ static int mpr_spi_xfer(struct mpr_data *data, const u8 cmd, const u8 pkt_len) xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; xfers[1].tx_buf = buf->tx; - xfers[1].rx_buf = data->buffer; + xfers[1].rx_buf = data->rx_buf; xfers[1].len = pkt_len; return spi_sync_transfer(spi, xfers, ARRAY_SIZE(xfers)); From cf322f806d83b3e221b2b72550c4132b87fa15d9 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Wed, 14 Jan 2026 18:55:38 +0200 Subject: [PATCH 166/297] iio: pressure: mprls0025pa: introduce tx buffer Use a tx_buf that is part of the priv struct for transferring data to the sensor instead of relying on a devm_kzalloc()-ed array. Remove the .init operation in the process. Reviewed-by: Marcelo Schmitt Signed-off-by: Petre Rodan Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/mprls0025pa.c | 4 ---- drivers/iio/pressure/mprls0025pa.h | 3 ++- drivers/iio/pressure/mprls0025pa_i2c.c | 10 ++-------- drivers/iio/pressure/mprls0025pa_spi.c | 24 ++---------------------- 4 files changed, 6 insertions(+), 35 deletions(-) diff --git a/drivers/iio/pressure/mprls0025pa.c b/drivers/iio/pressure/mprls0025pa.c index 245d7ed82b6b..2bcd339dc84e 100644 --- a/drivers/iio/pressure/mprls0025pa.c +++ b/drivers/iio/pressure/mprls0025pa.c @@ -363,10 +363,6 @@ int mpr_common_probe(struct device *dev, const struct mpr_ops *ops, int irq) return dev_err_probe(dev, ret, "can't get and enable vdd supply\n"); - ret = data->ops->init(data->dev); - if (ret) - return ret; - ret = device_property_read_u32(dev, "honeywell,transfer-function", &func); if (ret) diff --git a/drivers/iio/pressure/mprls0025pa.h b/drivers/iio/pressure/mprls0025pa.h index 119ebb0ba567..9f43273e635f 100644 --- a/drivers/iio/pressure/mprls0025pa.h +++ b/drivers/iio/pressure/mprls0025pa.h @@ -55,6 +55,7 @@ enum mpr_func_id { * @chan.pres: pressure value * @chan.ts: timestamp * @rx_buf: raw conversion data + * @tx_buf: output buffer */ struct mpr_data { struct device *dev; @@ -76,10 +77,10 @@ struct mpr_data { aligned_s64 ts; } chan; u8 rx_buf[MPR_MEASUREMENT_RD_SIZE] __aligned(IIO_DMA_MINALIGN); + u8 tx_buf[MPR_MEASUREMENT_RD_SIZE]; }; struct mpr_ops { - int (*init)(struct device *dev); int (*read)(struct mpr_data *data, const u8 cmd, const u8 cnt); int (*write)(struct mpr_data *data, const u8 cmd, const u8 cnt); }; diff --git a/drivers/iio/pressure/mprls0025pa_i2c.c b/drivers/iio/pressure/mprls0025pa_i2c.c index 36da0059b19f..a0bbc6af9283 100644 --- a/drivers/iio/pressure/mprls0025pa_i2c.c +++ b/drivers/iio/pressure/mprls0025pa_i2c.c @@ -17,11 +17,6 @@ #include "mprls0025pa.h" -static int mpr_i2c_init(struct device *unused) -{ - return 0; -} - static int mpr_i2c_read(struct mpr_data *data, const u8 unused, const u8 cnt) { int ret; @@ -44,9 +39,9 @@ static int mpr_i2c_write(struct mpr_data *data, const u8 cmd, const u8 unused) { int ret; struct i2c_client *client = to_i2c_client(data->dev); - u8 wdata[MPR_PKT_SYNC_LEN] = { cmd }; - ret = i2c_master_send(client, wdata, MPR_PKT_SYNC_LEN); + data->tx_buf[0] = cmd; + ret = i2c_master_send(client, data->tx_buf, MPR_PKT_SYNC_LEN); if (ret < 0) return ret; else if (ret != MPR_PKT_SYNC_LEN) @@ -56,7 +51,6 @@ static int mpr_i2c_write(struct mpr_data *data, const u8 cmd, const u8 unused) } static const struct mpr_ops mpr_i2c_ops = { - .init = mpr_i2c_init, .read = mpr_i2c_read, .write = mpr_i2c_write, }; diff --git a/drivers/iio/pressure/mprls0025pa_spi.c b/drivers/iio/pressure/mprls0025pa_spi.c index 247b65226bb9..8c8c726f703f 100644 --- a/drivers/iio/pressure/mprls0025pa_spi.c +++ b/drivers/iio/pressure/mprls0025pa_spi.c @@ -19,34 +19,15 @@ #include "mprls0025pa.h" -struct mpr_spi_buf { - u8 tx[MPR_MEASUREMENT_RD_SIZE] __aligned(IIO_DMA_MINALIGN); -}; - -static int mpr_spi_init(struct device *dev) -{ - struct spi_device *spi = to_spi_device(dev); - struct mpr_spi_buf *buf; - - buf = devm_kzalloc(dev, sizeof(*buf), GFP_KERNEL); - if (!buf) - return -ENOMEM; - - spi_set_drvdata(spi, buf); - - return 0; -} - static int mpr_spi_xfer(struct mpr_data *data, const u8 cmd, const u8 pkt_len) { struct spi_device *spi = to_spi_device(data->dev); - struct mpr_spi_buf *buf = spi_get_drvdata(spi); struct spi_transfer xfers[2] = { }; if (pkt_len > MPR_MEASUREMENT_RD_SIZE) return -EOVERFLOW; - buf->tx[0] = cmd; + data->tx_buf[0] = cmd; /* * Dummy transfer with no data, just cause a 2.5us+ delay between the CS assert @@ -55,7 +36,7 @@ static int mpr_spi_xfer(struct mpr_data *data, const u8 cmd, const u8 pkt_len) xfers[0].delay.value = 2500; xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; - xfers[1].tx_buf = buf->tx; + xfers[1].tx_buf = data->tx_buf; xfers[1].rx_buf = data->rx_buf; xfers[1].len = pkt_len; @@ -63,7 +44,6 @@ static int mpr_spi_xfer(struct mpr_data *data, const u8 cmd, const u8 pkt_len) } static const struct mpr_ops mpr_spi_ops = { - .init = mpr_spi_init, .read = mpr_spi_xfer, .write = mpr_spi_xfer, }; From 4edab7b08783f06679de0d24318d92ffc1dfd537 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Wed, 14 Jan 2026 18:55:39 +0200 Subject: [PATCH 167/297] iio: pressure: mprls0025pa: move memset to core Move memset() from the bus specific code into core. Zeroing out the buffer is performed because the sensor has noticeable latch-up sensitivity and in some cases it clamps the MISO signal to GND in sync with SCLK [1]. A raw conversion of zero is out of bounds since valid values have to be between output_min and output_max (and the smallest output_min is 2.5% of 2^24 = 419430). The user is expected to discard out of bounds pressure values. Given the fact that we can't follow the behaviour of all SPI controllers when faced to this clamping of an output signal, a raw conversion of zero is used as an early warning in case the low level SPI API reacts unexpectedly. Link: https://e2e.ti.com/support/processors-group/processors/f/processors-forum/1588325/am3358-spi-tx-data-corruption [1] Signed-off-by: Petre Rodan Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/mprls0025pa.c | 2 ++ drivers/iio/pressure/mprls0025pa_i2c.c | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/iio/pressure/mprls0025pa.c b/drivers/iio/pressure/mprls0025pa.c index 2bcd339dc84e..a7041a503bde 100644 --- a/drivers/iio/pressure/mprls0025pa.c +++ b/drivers/iio/pressure/mprls0025pa.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -247,6 +248,7 @@ static int mpr_read_pressure(struct mpr_data *data, s32 *press) } } + memset(data->rx_buf, 0, sizeof(data->rx_buf)); ret = data->ops->read(data, MPR_CMD_NOP, MPR_PKT_NOP_LEN); if (ret < 0) return ret; diff --git a/drivers/iio/pressure/mprls0025pa_i2c.c b/drivers/iio/pressure/mprls0025pa_i2c.c index a0bbc6af9283..0fe8cfe0d7e7 100644 --- a/drivers/iio/pressure/mprls0025pa_i2c.c +++ b/drivers/iio/pressure/mprls0025pa_i2c.c @@ -25,7 +25,6 @@ static int mpr_i2c_read(struct mpr_data *data, const u8 unused, const u8 cnt) if (cnt > MPR_MEASUREMENT_RD_SIZE) return -EOVERFLOW; - memset(data->rx_buf, 0, MPR_MEASUREMENT_RD_SIZE); ret = i2c_master_recv(client, data->rx_buf, cnt); if (ret < 0) return ret; From 84e15e1a6e103e5b57852d90ac686d7f0d0d8b0a Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Wed, 14 Jan 2026 18:55:40 +0200 Subject: [PATCH 168/297] iio: pressure: mprls0025pa: stricter checks for the status byte Make sure a valid conversion comes with a status byte that only has the MPR_ST_POWER bit set. Return -EBUSY if also MPR_ST_BUSY is set or -EIO otherwise. Reviewed-by: Marcelo Schmitt Signed-off-by: Petre Rodan Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/mprls0025pa.c | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/drivers/iio/pressure/mprls0025pa.c b/drivers/iio/pressure/mprls0025pa.c index a7041a503bde..31ec2bae84b1 100644 --- a/drivers/iio/pressure/mprls0025pa.c +++ b/drivers/iio/pressure/mprls0025pa.c @@ -198,9 +198,10 @@ static void mpr_reset(struct mpr_data *data) * * Context: The function can sleep and data->lock should be held when calling it * Return: - * * 0 - OK, the pressure value could be read - * * -ETIMEDOUT - Timeout while waiting for the EOC interrupt or busy flag is - * still set after nloops attempts of reading + * * 0 - OK, the pressure value could be read + * * -EBUSY - Sensor does not have a new conversion ready + * * -ETIMEDOUT - Timeout while waiting for the EOC interrupt + * * -EIO - Invalid status byte received from sensor */ static int mpr_read_pressure(struct mpr_data *data, s32 *press) { @@ -253,10 +254,25 @@ static int mpr_read_pressure(struct mpr_data *data, s32 *press) if (ret < 0) return ret; - if (data->rx_buf[0] & MPR_ST_ERR_FLAG) { + /* + * Status byte flags + * bit7 SANITY_CHK - must always be 0 + * bit6 MPR_ST_POWER - 1 if device is powered + * bit5 MPR_ST_BUSY - 1 if device has no new conversion ready + * bit4 SANITY_CHK - must always be 0 + * bit3 SANITY_CHK - must always be 0 + * bit2 MEMORY_ERR - 1 if integrity test has failed + * bit1 SANITY_CHK - must always be 0 + * bit0 MATH_ERR - 1 during internal math saturation error + */ + + if (data->rx_buf[0] == (MPR_ST_POWER | MPR_ST_BUSY)) + return -EBUSY; + + if (data->rx_buf[0] != MPR_ST_POWER) { dev_err(data->dev, - "unexpected status byte %02x\n", data->rx_buf[0]); - return -ETIMEDOUT; + "unexpected status byte 0x%02x\n", data->rx_buf[0]); + return -EIO; } *press = get_unaligned_be24(&data->rx_buf[1]); From 500b36ee448c0e533acaa186383f0311bbb3dfa5 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Wed, 14 Jan 2026 18:55:41 +0200 Subject: [PATCH 169/297] iio: pressure: mprls0025pa: change measurement sequence Implement a measurement sequence that does not involve a one byte read of the status byte before reading the conversion. The sensor's conversions should be read either once the EoC interrupt has triggered or 5ms after the 0xaa command. See Options 1 and 2 respectively in Tables 16 (page 15) and 18 (page 18) of the datasheet. Note that Honeywell's example code also covered in the datasheet follows Option 2 for both i2c and SPI. The datasheet does not specify any of the retry parameters that are currently implemented in the driver. A simple 5+ms sleep as specified in Option 2 is enough for a valid measurement sequence. The change also gets rid of the code duplication tied to the verification of the status byte. This change only affects users that do not define the EoC interrupt in the device tree. Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/micropressure-mpr-series/documents/sps-siot-mpr-series-datasheet-32332628-ciid-172626.pdf?download=false Signed-off-by: Petre Rodan Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/mprls0025pa.c | 32 +++--------------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/drivers/iio/pressure/mprls0025pa.c b/drivers/iio/pressure/mprls0025pa.c index 31ec2bae84b1..b1122ace6bac 100644 --- a/drivers/iio/pressure/mprls0025pa.c +++ b/drivers/iio/pressure/mprls0025pa.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -41,10 +42,6 @@ /* bits in status byte */ #define MPR_ST_POWER BIT(6) /* device is powered */ #define MPR_ST_BUSY BIT(5) /* device is busy */ -#define MPR_ST_MEMORY BIT(2) /* integrity test passed */ -#define MPR_ST_MATH BIT(0) /* internal math saturation */ - -#define MPR_ST_ERR_FLAG (MPR_ST_BUSY | MPR_ST_MEMORY | MPR_ST_MATH) /* * support _RAW sysfs interface: @@ -206,8 +203,7 @@ static void mpr_reset(struct mpr_data *data) static int mpr_read_pressure(struct mpr_data *data, s32 *press) { struct device *dev = data->dev; - int ret, i; - int nloops = 10; + int ret; reinit_completion(&data->completion); @@ -224,29 +220,7 @@ static int mpr_read_pressure(struct mpr_data *data, s32 *press) return -ETIMEDOUT; } } else { - /* wait until status indicates data is ready */ - for (i = 0; i < nloops; i++) { - /* - * datasheet only says to wait at least 5 ms for the - * data but leave the maximum response time open - * --> let's try it nloops (10) times which seems to be - * quite long - */ - usleep_range(5000, 10000); - ret = data->ops->read(data, MPR_CMD_NOP, 1); - if (ret < 0) { - dev_err(dev, - "error while reading, status: %d\n", - ret); - return ret; - } - if (!(data->rx_buf[0] & MPR_ST_ERR_FLAG)) - break; - } - if (i == nloops) { - dev_err(dev, "timeout while reading\n"); - return -ETIMEDOUT; - } + fsleep(5 * USEC_PER_MSEC); } memset(data->rx_buf, 0, sizeof(data->rx_buf)); From 0b6a86a049f558bf2496c91b1259e1dc34aed938 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Wed, 14 Jan 2026 18:55:42 +0200 Subject: [PATCH 170/297] iio: pressure: mprls0025pa: add copyright line Add copyright line to the core driver. Signed-off-by: Petre Rodan Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/mprls0025pa.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/iio/pressure/mprls0025pa.c b/drivers/iio/pressure/mprls0025pa.c index b1122ace6bac..587d0dcad89b 100644 --- a/drivers/iio/pressure/mprls0025pa.c +++ b/drivers/iio/pressure/mprls0025pa.c @@ -3,6 +3,7 @@ * MPRLS0025PA - Honeywell MicroPressure pressure sensor series driver * * Copyright (c) Andreas Klinger + * Copyright (c) 2023-2025 Petre Rodan * * Data sheet: * https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/micropressure-mpr-series/documents/sps-siot-mpr-series-datasheet-32332628-ciid-172626.pdf From bac5e3d20c45392120c4298c2f66fc4387776b80 Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Wed, 14 Jan 2026 10:45:50 +0000 Subject: [PATCH 171/297] iio: adc: ad9467: include two's complement in default mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All supported drivers currently implicitly use two's complement mode. Make this clear by declaring two's complement in the default output mode. Calibration mode uses offset binary, so change the output mode only when running the calibration or other test mode. Reviewed-by: Nuno Sá Signed-off-by: Tomas Melin Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad9467.c | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/drivers/iio/adc/ad9467.c b/drivers/iio/adc/ad9467.c index 59c3fa3bcc9b..9cfe66425d4e 100644 --- a/drivers/iio/adc/ad9467.c +++ b/drivers/iio/adc/ad9467.c @@ -5,6 +5,7 @@ * Copyright 2012-2020 Analog Devices Inc. */ +#include #include #include #include @@ -72,6 +73,7 @@ #define AN877_ADC_OUTPUT_MODE_OFFSET_BINARY 0x0 #define AN877_ADC_OUTPUT_MODE_TWOS_COMPLEMENT 0x1 #define AN877_ADC_OUTPUT_MODE_GRAY_CODE 0x2 +#define AN877_ADC_OUTPUT_MODE_MASK GENMASK(1, 0) /* AN877_ADC_REG_OUTPUT_PHASE */ #define AN877_ADC_OUTPUT_EVEN_ODD_MODE_EN 0x20 @@ -85,7 +87,7 @@ */ #define CHIPID_AD9211 0x06 -#define AD9211_DEF_OUTPUT_MODE 0x00 +#define AD9211_DEF_OUTPUT_MODE 0x01 #define AD9211_REG_VREF_MASK GENMASK(4, 0) /* @@ -93,7 +95,7 @@ */ #define CHIPID_AD9265 0x64 -#define AD9265_DEF_OUTPUT_MODE 0x40 +#define AD9265_DEF_OUTPUT_MODE 0x41 #define AD9265_REG_VREF_MASK 0xC0 /* @@ -101,7 +103,7 @@ */ #define CHIPID_AD9434 0x6A -#define AD9434_DEF_OUTPUT_MODE 0x00 +#define AD9434_DEF_OUTPUT_MODE 0x01 #define AD9434_REG_VREF_MASK 0xC0 /* @@ -109,7 +111,7 @@ */ #define CHIPID_AD9467 0x50 -#define AD9467_DEF_OUTPUT_MODE 0x08 +#define AD9467_DEF_OUTPUT_MODE 0x09 #define AD9467_REG_VREF_MASK 0x0F /* @@ -117,6 +119,7 @@ */ #define CHIPID_AD9643 0x82 +#define AD9643_DEF_OUTPUT_MODE 0x01 #define AD9643_REG_VREF_MASK 0x1F /* @@ -124,6 +127,7 @@ */ #define CHIPID_AD9652 0xC1 +#define AD9652_DEF_OUTPUT_MODE 0x01 #define AD9652_REG_VREF_MASK 0xC0 /* @@ -131,6 +135,7 @@ */ #define CHIPID_AD9649 0x6F +#define AD9649_DEF_OUTPUT_MODE 0x01 #define AD9649_TEST_POINTS 8 #define AD9647_MAX_TEST_POINTS 32 @@ -461,6 +466,7 @@ static const struct ad9467_chip_info ad9643_chip_tbl = { .test_mask = BIT(AN877_ADC_TESTMODE_RAMP) | GENMASK(AN877_ADC_TESTMODE_MIXED_BIT_FREQUENCY, AN877_ADC_TESTMODE_OFF), .test_mask_len = AN877_ADC_TESTMODE_RAMP + 1, + .default_output_mode = AD9643_DEF_OUTPUT_MODE, .vref_mask = AD9643_REG_VREF_MASK, .has_dco = true, .has_dco_invert = true, @@ -479,6 +485,7 @@ static const struct ad9467_chip_info ad9649_chip_tbl = { .test_mask = GENMASK(AN877_ADC_TESTMODE_MIXED_BIT_FREQUENCY, AN877_ADC_TESTMODE_OFF), .test_mask_len = AN877_ADC_TESTMODE_MIXED_BIT_FREQUENCY + 1, + .default_output_mode = AD9649_DEF_OUTPUT_MODE, .has_dco = true, .has_dco_invert = true, .dco_en = AN877_ADC_DCO_DELAY_ENABLE, @@ -496,6 +503,7 @@ static const struct ad9467_chip_info ad9652_chip_tbl = { .test_mask = GENMASK(AN877_ADC_TESTMODE_ONE_ZERO_TOGGLE, AN877_ADC_TESTMODE_OFF), .test_mask_len = AN877_ADC_TESTMODE_ONE_ZERO_TOGGLE + 1, + .default_output_mode = AD9652_DEF_OUTPUT_MODE, .vref_mask = AD9652_REG_VREF_MASK, .has_dco = true, }; @@ -671,10 +679,14 @@ static int ad9467_backend_testmode_off(struct ad9467_state *st, static int ad9647_calibrate_prepare(struct ad9467_state *st) { + unsigned int cmode; unsigned int c; int ret; - ret = ad9467_outputmode_set(st, st->info->default_output_mode); + cmode = st->info->default_output_mode; + FIELD_MODIFY(AN877_ADC_OUTPUT_MODE_MASK, &cmode, + AN877_ADC_OUTPUT_MODE_OFFSET_BINARY); + ret = ad9467_outputmode_set(st, cmode); if (ret) return ret; @@ -778,7 +790,7 @@ static int ad9647_calibrate_stop(struct ad9467_state *st) return ret; } - mode = st->info->default_output_mode | AN877_ADC_OUTPUT_MODE_TWOS_COMPLEMENT; + mode = st->info->default_output_mode; return ad9467_outputmode_set(st, mode); } @@ -1174,12 +1186,17 @@ static ssize_t ad9467_chan_test_mode_write(struct file *file, if (ret) return ret; - out_mode = st->info->default_output_mode | AN877_ADC_OUTPUT_MODE_TWOS_COMPLEMENT; + out_mode = st->info->default_output_mode; ret = ad9467_outputmode_set(st, out_mode); if (ret) return ret; } else { - ret = ad9467_outputmode_set(st, st->info->default_output_mode); + unsigned int cmode; + + cmode = st->info->default_output_mode; + FIELD_MODIFY(AN877_ADC_OUTPUT_MODE_MASK, &cmode, + AN877_ADC_OUTPUT_MODE_OFFSET_BINARY); + ret = ad9467_outputmode_set(st, cmode); if (ret) return ret; From a99b000f92413f254a6dc0f6ab88b310152f9485 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Thu, 15 Jan 2026 18:53:48 +0100 Subject: [PATCH 172/297] iio: accel: adxl380: Store sampling frequency index in odr struct member MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ADXL380 driver assumes that acceleration samples are always retrieved via the high-performance DSM signal path; as a result, the sampling frequency value depends exclusively on the decimation filter settings in the TRIG_CFG register. In preparation for adding support for sampling frequency values that rely on the low-power SAR signal path (on which the decimation filters are not supported), use the (currently unused) 'odr' member of struct adxl380_state to store the sampling frequency value, and when userspace requests the current frequency value, retrieve it from the struct instead of calculating it from the decimation filter settings. Signed-off-by: Francesco Lavra Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl380.c | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/drivers/iio/accel/adxl380.c b/drivers/iio/accel/adxl380.c index 6d5f1a0d51e9..28caa88d7c32 100644 --- a/drivers/iio/accel/adxl380.c +++ b/drivers/iio/accel/adxl380.c @@ -417,17 +417,7 @@ static int adxl380_read_chn(struct adxl380_state *st, u8 addr) static int adxl380_get_odr(struct adxl380_state *st, int *odr) { - int ret; - unsigned int trig_cfg, odr_idx; - - ret = regmap_read(st->regmap, ADXL380_TRIG_CFG_REG, &trig_cfg); - if (ret) - return ret; - - odr_idx = (FIELD_GET(ADXL380_TRIG_CFG_SINC_RATE_MSK, trig_cfg) << 1) | - (FIELD_GET(ADXL380_TRIG_CFG_DEC_2X_MSK, trig_cfg) & 1); - - *odr = st->chip_info->samp_freq_tbl[odr_idx]; + *odr = st->chip_info->samp_freq_tbl[st->odr]; return 0; } @@ -500,6 +490,7 @@ static int adxl380_set_odr(struct adxl380_state *st, u8 odr) if (ret) return ret; + st->odr = odr; ret = adxl380_set_measure_en(st, true); if (ret) return ret; From fabae7558722dcf41a9137f6f0f82462867d7c96 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Thu, 15 Jan 2026 18:53:49 +0100 Subject: [PATCH 173/297] iio: accel: adxl380: Introduce helper function for activity detection Motion detection functionalities (such as activity and inactivity detection) are only available when the chip is in a low-power mode; this affects the available sampling frequency values. In preparation for adding support for a new frequency value, introduce a helper function that checks whether activity/inactivity detection is currently enabled; this function will be reused in a future commit to determine what frequency values are available at any given time. No functional changes. Signed-off-by: Francesco Lavra Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl380.c | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/drivers/iio/accel/adxl380.c b/drivers/iio/accel/adxl380.c index 28caa88d7c32..021d19a44985 100644 --- a/drivers/iio/accel/adxl380.c +++ b/drivers/iio/accel/adxl380.c @@ -232,14 +232,35 @@ bool adxl380_readable_noinc_reg(struct device *dev, unsigned int reg) } EXPORT_SYMBOL_NS_GPL(adxl380_readable_noinc_reg, "IIO_ADXL380"); +static int adxl380_act_inact_enabled(struct adxl380_state *st, bool *enabled) +{ + unsigned int act_inact_ctl; + int ret; + + if (!st->chip_info->has_low_power) { + *enabled = false; + return 0; + } + + ret = regmap_read(st->regmap, ADXL380_ACT_INACT_CTL_REG, &act_inact_ctl); + if (ret) + return ret; + + *enabled = FIELD_GET(ADXL380_ACT_EN_MSK, act_inact_ctl) || + FIELD_GET(ADXL380_INACT_EN_MSK, act_inact_ctl); + + return 0; +} + static int adxl380_set_measure_en(struct adxl380_state *st, bool en) { int ret; - unsigned int act_inact_ctl; u8 op_mode = ADXL380_OP_MODE_STANDBY; if (en) { - ret = regmap_read(st->regmap, ADXL380_ACT_INACT_CTL_REG, &act_inact_ctl); + bool act_inact_enabled; + + ret = adxl380_act_inact_enabled(st, &act_inact_enabled); if (ret) return ret; @@ -248,9 +269,7 @@ static int adxl380_set_measure_en(struct adxl380_state *st, bool en) * mode and for devices that support low power modes. Otherwise * go straight to measure mode (same bits as ADXL380_OP_MODE_HP). */ - if (st->chip_info->has_low_power && - (FIELD_GET(ADXL380_ACT_EN_MSK, act_inact_ctl) || - FIELD_GET(ADXL380_INACT_EN_MSK, act_inact_ctl))) + if (act_inact_enabled) op_mode = ADXL380_OP_MODE_VLP; else op_mode = ADXL380_OP_MODE_HP; From f65de31fb6aba70133a0cb57bba6f84b5fb4d695 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Thu, 15 Jan 2026 18:53:50 +0100 Subject: [PATCH 174/297] iio: accel: adxl380: Add support for 1 kHz sampling frequency In sensor variants (such as ADXL380 and ADXL382) that support low-power mode, the SAR signal path allows sampling acceleration data at lower rates; more specifically, when the sensor operates in VLP mode, the sampling frequency is 1 kHz. To add support for the 1kHz sampling frequency value, modify the operating mode selection logic to take into account the sampling frequency, and configure the decimation filters only when applicable (i.e. when using a sampling frequency that relies on the DSM signal path); in addition, constrain the available sampling frequency values based on whether the sensor is operating in low-power mode. Signed-off-by: Francesco Lavra Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl380.c | 83 +++++++++++++++++++++++++++---------- drivers/iio/accel/adxl380.h | 10 ++++- 2 files changed, 71 insertions(+), 22 deletions(-) diff --git a/drivers/iio/accel/adxl380.c b/drivers/iio/accel/adxl380.c index 021d19a44985..ba550142866a 100644 --- a/drivers/iio/accel/adxl380.c +++ b/drivers/iio/accel/adxl380.c @@ -266,10 +266,12 @@ static int adxl380_set_measure_en(struct adxl380_state *st, bool en) /* * Activity/Inactivity detection available only in VLP/ULP - * mode and for devices that support low power modes. Otherwise - * go straight to measure mode (same bits as ADXL380_OP_MODE_HP). + * mode and for devices that support low power modes. */ if (act_inact_enabled) + st->odr = ADXL380_ODR_VLP; + + if (st->odr == ADXL380_ODR_VLP) op_mode = ADXL380_OP_MODE_VLP; else op_mode = ADXL380_OP_MODE_HP; @@ -497,17 +499,22 @@ static int adxl380_set_odr(struct adxl380_state *st, u8 odr) if (ret) return ret; - ret = regmap_update_bits(st->regmap, ADXL380_TRIG_CFG_REG, - ADXL380_TRIG_CFG_DEC_2X_MSK, - FIELD_PREP(ADXL380_TRIG_CFG_DEC_2X_MSK, odr & 1)); - if (ret) - return ret; + if (odr >= ADXL380_ODR_DSM) { + u8 mul = odr - ADXL380_ODR_DSM; + u8 field; - ret = regmap_update_bits(st->regmap, ADXL380_TRIG_CFG_REG, - ADXL380_TRIG_CFG_SINC_RATE_MSK, - FIELD_PREP(ADXL380_TRIG_CFG_SINC_RATE_MSK, odr >> 1)); - if (ret) - return ret; + field = FIELD_PREP(ADXL380_TRIG_CFG_DEC_2X_MSK, mul & 1); + ret = regmap_update_bits(st->regmap, ADXL380_TRIG_CFG_REG, + ADXL380_TRIG_CFG_DEC_2X_MSK, field); + if (ret) + return ret; + + field = FIELD_PREP(ADXL380_TRIG_CFG_SINC_RATE_MSK, mul >> 1); + ret = regmap_update_bits(st->regmap, ADXL380_TRIG_CFG_REG, + ADXL380_TRIG_CFG_SINC_RATE_MSK, field); + if (ret) + return ret; + } st->odr = odr; ret = adxl380_set_measure_en(st, true); @@ -1148,6 +1155,32 @@ static const struct iio_buffer_setup_ops adxl380_buffer_ops = { .predisable = adxl380_buffer_predisable, }; +static int adxl380_samp_freq_avail(struct adxl380_state *st, const int **vals, + int *length) +{ + bool act_inact_enabled; + int ret; + + if (!st->chip_info->has_low_power) { + *vals = st->chip_info->samp_freq_tbl + ADXL380_ODR_DSM; + *length = ADXL380_ODR_MAX - ADXL380_ODR_DSM; + return 0; + } + + ret = adxl380_act_inact_enabled(st, &act_inact_enabled); + if (ret) + return 0; + + /* + * Motion detection is only functional in low-power mode, and this + * affects the available sampling frequencies. + */ + *vals = st->chip_info->samp_freq_tbl; + *length = act_inact_enabled ? ADXL380_ODR_DSM : ADXL380_ODR_MAX; + + return 0; +} + static int adxl380_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long info) @@ -1228,6 +1261,7 @@ static int adxl380_read_avail(struct iio_dev *indio_dev, long mask) { struct adxl380_state *st = iio_priv(indio_dev); + int ret; if (chan->type != IIO_ACCEL) return -EINVAL; @@ -1239,9 +1273,11 @@ static int adxl380_read_avail(struct iio_dev *indio_dev, *length = ARRAY_SIZE(st->chip_info->scale_tbl) * 2; return IIO_AVAIL_LIST; case IIO_CHAN_INFO_SAMP_FREQ: - *vals = (const int *)st->chip_info->samp_freq_tbl; + ret = adxl380_samp_freq_avail(st, vals, length); + if (ret) + return ret; + *type = IIO_VAL_INT; - *length = ARRAY_SIZE(st->chip_info->samp_freq_tbl); return IIO_AVAIL_LIST; case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: *vals = (const int *)st->lpf_tbl; @@ -1264,12 +1300,16 @@ static int adxl380_write_raw(struct iio_dev *indio_dev, int val, int val2, long info) { struct adxl380_state *st = iio_priv(indio_dev); - int odr_index, lpf_index, hpf_index, range_index; + const int *freq_vals; + int odr_index, lpf_index, hpf_index, range_index, freq_count, ret; switch (info) { case IIO_CHAN_INFO_SAMP_FREQ: - odr_index = adxl380_find_match_1d_tbl(st->chip_info->samp_freq_tbl, - ARRAY_SIZE(st->chip_info->samp_freq_tbl), + ret = adxl380_samp_freq_avail(st, &freq_vals, &freq_count); + if (ret) + return ret; + + odr_index = adxl380_find_match_1d_tbl(freq_vals, freq_count, val); return adxl380_set_odr(st, odr_index); case IIO_CHAN_INFO_CALIBBIAS: @@ -1631,7 +1671,7 @@ const struct adxl380_chip_info adxl318_chip_info = { [ADXL380_OP_MODE_8G_RANGE] = { 0, 2615434 }, [ADXL380_OP_MODE_16G_RANGE] = { 0, 5229886 }, }, - .samp_freq_tbl = { 8000, 16000, 32000 }, + .samp_freq_tbl = { 0, 8000, 16000, 32000 }, /* * The datasheet defines an intercept of 550 LSB at 25 degC * and a sensitivity of 10.2 LSB/C. @@ -1649,7 +1689,7 @@ const struct adxl380_chip_info adxl319_chip_info = { [ADXL382_OP_MODE_30G_RANGE] = { 0, 9806650 }, [ADXL382_OP_MODE_60G_RANGE] = { 0, 19613300 }, }, - .samp_freq_tbl = { 16000, 32000, 64000 }, + .samp_freq_tbl = { 0, 16000, 32000, 64000 }, /* * The datasheet defines an intercept of 550 LSB at 25 degC * and a sensitivity of 10.2 LSB/C. @@ -1667,7 +1707,7 @@ const struct adxl380_chip_info adxl380_chip_info = { [ADXL380_OP_MODE_8G_RANGE] = { 0, 2615434 }, [ADXL380_OP_MODE_16G_RANGE] = { 0, 5229886 }, }, - .samp_freq_tbl = { 8000, 16000, 32000 }, + .samp_freq_tbl = { 1000, 8000, 16000, 32000 }, /* * The datasheet defines an intercept of 470 LSB at 25 degC * and a sensitivity of 10.2 LSB/C. @@ -1687,7 +1727,7 @@ const struct adxl380_chip_info adxl382_chip_info = { [ADXL382_OP_MODE_30G_RANGE] = { 0, 9806650 }, [ADXL382_OP_MODE_60G_RANGE] = { 0, 19613300 }, }, - .samp_freq_tbl = { 16000, 32000, 64000 }, + .samp_freq_tbl = { 1000, 16000, 32000, 64000 }, /* * The datasheet defines an intercept of 570 LSB at 25 degC * and a sensitivity of 10.2 LSB/C. @@ -1926,6 +1966,7 @@ int adxl380_probe(struct device *dev, struct regmap *regmap, st->dev = dev; st->regmap = regmap; st->chip_info = chip_info; + st->odr = ADXL380_ODR_DSM; mutex_init(&st->lock); diff --git a/drivers/iio/accel/adxl380.h b/drivers/iio/accel/adxl380.h index e67c5aab8efc..d2c260c8b2fa 100644 --- a/drivers/iio/accel/adxl380.h +++ b/drivers/iio/accel/adxl380.h @@ -8,10 +8,18 @@ #ifndef _ADXL380_H_ #define _ADXL380_H_ +enum adxl380_odr { + ADXL380_ODR_VLP, + ADXL380_ODR_DSM, + ADXL380_ODR_DSM_2X, + ADXL380_ODR_DSM_4X, + ADXL380_ODR_MAX +}; + struct adxl380_chip_info { const char *name; const int scale_tbl[3][2]; - const int samp_freq_tbl[3]; + const int samp_freq_tbl[ADXL380_ODR_MAX]; const struct iio_info *info; const int temp_offset; const u16 chip_id; From 188c33824098ced363676b4e63f062b7a747ed81 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Thu, 15 Jan 2026 15:24:16 +0100 Subject: [PATCH 175/297] iio: imu: smi330: Convert to common field_{get,prep}() helpers Drop the driver-specific smi330_field_get() and smi330_field_prep() macros, in favor of the globally available variants from . Signed-off-by: Geert Uytterhoeven Signed-off-by: Jonathan Cameron --- drivers/iio/imu/smi330/smi330_core.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/iio/imu/smi330/smi330_core.c b/drivers/iio/imu/smi330/smi330_core.c index 0cf673b44b62..7ec5ae7f521a 100644 --- a/drivers/iio/imu/smi330/smi330_core.c +++ b/drivers/iio/imu/smi330/smi330_core.c @@ -67,10 +67,6 @@ #define SMI330_CHIP_ID 0x42 #define SMI330_SOFT_RESET_DELAY 2000 -/* Non-constant mask variant of FIELD_GET() and FIELD_PREP() */ -#define smi330_field_get(_mask, _reg) (((_reg) & (_mask)) >> (ffs(_mask) - 1)) -#define smi330_field_prep(_mask, _val) (((_val) << (ffs(_mask) - 1)) & (_mask)) - #define SMI330_ACCEL_CHANNEL(_axis) { \ .type = IIO_ACCEL, \ .modified = 1, \ @@ -361,7 +357,7 @@ static int smi330_get_sensor_config(struct smi330_data *data, if (ret) return ret; - reg_val = smi330_field_get(attr->mask, reg_val); + reg_val = field_get(attr->mask, reg_val); if (attr->type == IIO_VAL_INT) { for (i = 0; i < attr->len; i++) { @@ -410,7 +406,7 @@ static int smi330_set_sensor_config(struct smi330_data *data, if (ret) return ret; - reg_val = smi330_field_prep(attr->mask, reg_val); + reg_val = field_prep(attr->mask, reg_val); ret = regmap_update_bits(data->regmap, reg, attr->mask, reg_val); if (ret) return ret; From a35df4c1182eec27f13922e963b8df0b88594fe2 Mon Sep 17 00:00:00 2001 From: Shrikant Raskar Date: Thu, 15 Jan 2026 13:57:37 +0530 Subject: [PATCH 176/297] iio: proximity: rfd77402: Reorder header includes Reorder header includes to follow kernel include ordering conventions. Signed-off-by: Shrikant Raskar Signed-off-by: Jonathan Cameron --- drivers/iio/proximity/rfd77402.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/proximity/rfd77402.c b/drivers/iio/proximity/rfd77402.c index 3262af6f6882..4499939215e7 100644 --- a/drivers/iio/proximity/rfd77402.c +++ b/drivers/iio/proximity/rfd77402.c @@ -10,9 +10,9 @@ * https://media.digikey.com/pdf/Data%20Sheets/RF%20Digital%20PDFs/RFD77402.pdf */ -#include -#include #include +#include +#include #include From 1feb0377b9b816f89a04fc381eb19fc6bac9f4a4 Mon Sep 17 00:00:00 2001 From: Antonio Borneo Date: Thu, 8 Jan 2026 16:24:27 +0100 Subject: [PATCH 177/297] coresight: etm3x: Fix cpulocked warning on cpuhp When changes [1] and [2] have been applied to the driver etm4x, the same modifications have been also collapsed in [3] and applied in one shot to the driver etm3x. While doing this, the driver etm3x has not been aligned to etm4x on the use of non cpuslocked version of cpuhp callback setup APIs. The current code triggers two run-time warnings when the kernel is compiled with CONFIG_PROVE_LOCKING=y. Use non cpuslocked version of cpuhp callback setup APIs in driver etm3x, aligning it to the driver etm4x. [1] commit 2d1a8bfb61ec ("coresight: etm4x: Fix etm4_count race by moving cpuhp callbacks to init") [2] commit 22a550a306ad ("coresight: etm4x: Allow etm4x to be built as a module") [3] commit 97fe626ce64c ("coresight: etm3x: Allow etm3x to be built as a module") Fixes: 97fe626ce64c ("coresight: etm3x: Allow etm3x to be built as a module") Signed-off-by: Antonio Borneo Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20260108152427.357379-1-antonio.borneo@foss.st.com --- drivers/hwtracing/coresight/coresight-etm3x-core.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm3x-core.c b/drivers/hwtracing/coresight/coresight-etm3x-core.c index 57e4a21c8fdd..a547a6d2e0bd 100644 --- a/drivers/hwtracing/coresight/coresight-etm3x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm3x-core.c @@ -800,16 +800,16 @@ static int __init etm_hp_setup(void) { int ret; - ret = cpuhp_setup_state_nocalls_cpuslocked(CPUHP_AP_ARM_CORESIGHT_STARTING, - "arm/coresight:starting", - etm_starting_cpu, etm_dying_cpu); + ret = cpuhp_setup_state_nocalls(CPUHP_AP_ARM_CORESIGHT_STARTING, + "arm/coresight:starting", + etm_starting_cpu, etm_dying_cpu); if (ret) return ret; - ret = cpuhp_setup_state_nocalls_cpuslocked(CPUHP_AP_ONLINE_DYN, - "arm/coresight:online", - etm_online_cpu, NULL); + ret = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN, + "arm/coresight:online", + etm_online_cpu, NULL); /* HP dyn state ID returned in ret on success */ if (ret > 0) { From 2b3625a83245e414a0a7abe3906a9972a40b1940 Mon Sep 17 00:00:00 2001 From: Yuanfang Zhang Date: Wed, 3 Dec 2025 20:43:07 -0800 Subject: [PATCH 178/297] dt-bindings: arm: qcom: Add Coresight Interconnect TNOC Add device tree binding for Qualcomm Coresight Interconnect Trace Network On Chip (ITNOC). This TNOC acts as a CoreSight graph link that forwards trace data from a subsystem to the Aggregator TNOC, without aggregation or ATID functionality. Signed-off-by: Yuanfang Zhang Reviewed-by: Rob Herring (Arm) Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251203-itnoc-v5-1-5b97c63f2268@oss.qualcomm.com --- .../bindings/arm/qcom,coresight-itnoc.yaml | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 Documentation/devicetree/bindings/arm/qcom,coresight-itnoc.yaml diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-itnoc.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-itnoc.yaml new file mode 100644 index 000000000000..8936bb7c3e8e --- /dev/null +++ b/Documentation/devicetree/bindings/arm/qcom,coresight-itnoc.yaml @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/arm/qcom,coresight-itnoc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Interconnect Trace Network On Chip - ITNOC + +maintainers: + - Yuanfang Zhang + +description: + The Interconnect TNOC is a CoreSight graph link that forwards trace data + from a subsystem to the Aggregator TNOC. Compared to Aggregator TNOC, it + does not have aggregation and ATID functionality. + +properties: + $nodename: + pattern: "^itnoc(@[0-9a-f]+)?$" + + compatible: + const: qcom,coresight-itnoc + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + items: + - const: apb + + in-ports: + $ref: /schemas/graph.yaml#/properties/ports + + patternProperties: + '^port(@[0-9a-f]{1,2})?$': + description: Input connections from CoreSight Trace Bus + $ref: /schemas/graph.yaml#/properties/port + + out-ports: + $ref: /schemas/graph.yaml#/properties/ports + additionalProperties: false + + properties: + port: + description: out connections to aggregator TNOC + $ref: /schemas/graph.yaml#/properties/port + +required: + - compatible + - reg + - clocks + - clock-names + - in-ports + - out-ports + +additionalProperties: false + +examples: + - | + itnoc@109ac000 { + compatible = "qcom,coresight-itnoc"; + reg = <0x109ac000 0x1000>; + + clocks = <&aoss_qmp>; + clock-names = "apb"; + + in-ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + tn_ic_in_tpdm_dcc: endpoint { + remote-endpoint = <&tpdm_dcc_out_tn_ic>; + }; + }; + }; + + out-ports { + port { + tn_ic_out_tnoc_aggr: endpoint { + /* to Aggregator TNOC input */ + remote-endpoint = <&tn_ag_in_tn_ic>; + }; + }; + }; + }; +... From 5799dee92dc244d750043373bf2f634e13398d52 Mon Sep 17 00:00:00 2001 From: Yuanfang Zhang Date: Wed, 3 Dec 2025 20:43:08 -0800 Subject: [PATCH 179/297] coresight-tnoc: add platform driver to support Interconnect TNOC This patch adds platform driver support for the CoreSight Interconnect TNOC, Interconnect TNOC is a CoreSight link that forwards trace data from a subsystem to the Aggregator TNOC. Compared to Aggregator TNOC, it does not have aggregation and ATID functionality. Key changes: - Add platform driver `coresight-itnoc` with device tree match support. - Refactor probe logic into a common `_tnoc_probe()` function. - Conditionally initialize ATID only for AMBA-based TNOC blocks. Signed-off-by: Yuanfang Zhang Reviewed-by: Leo Yan Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251203-itnoc-v5-2-5b97c63f2268@oss.qualcomm.com --- drivers/hwtracing/coresight/coresight-tnoc.c | 113 +++++++++++++++++-- 1 file changed, 102 insertions(+), 11 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-tnoc.c b/drivers/hwtracing/coresight/coresight-tnoc.c index ff9a0a9cfe96..affd8204b534 100644 --- a/drivers/hwtracing/coresight/coresight-tnoc.c +++ b/drivers/hwtracing/coresight/coresight-tnoc.c @@ -34,6 +34,7 @@ * @base: memory mapped base address for this component. * @dev: device node for trace_noc_drvdata. * @csdev: component vitals needed by the framework. + * @pclk: APB clock if present, otherwise NULL * @spinlock: serialize enable/disable operation. * @atid: id for the trace packet. */ @@ -41,8 +42,9 @@ struct trace_noc_drvdata { void __iomem *base; struct device *dev; struct coresight_device *csdev; + struct clk *pclk; spinlock_t spinlock; - u32 atid; + int atid; }; DEFINE_CORESIGHT_DEVLIST(trace_noc_devs, "traceNoc"); @@ -51,6 +53,12 @@ static void trace_noc_enable_hw(struct trace_noc_drvdata *drvdata) { u32 val; + /* No valid ATID, simply enable the unit */ + if (drvdata->atid == -EOPNOTSUPP) { + writel(TRACE_NOC_CTRL_PORTEN, drvdata->base + TRACE_NOC_CTRL); + return; + } + /* Set ATID */ writel_relaxed(drvdata->atid, drvdata->base + TRACE_NOC_XLD); @@ -124,6 +132,11 @@ static int trace_noc_init_default_data(struct trace_noc_drvdata *drvdata) { int atid; + if (!dev_is_amba(drvdata->dev)) { + drvdata->atid = -EOPNOTSUPP; + return 0; + } + atid = coresight_trace_id_get_system_id(); if (atid < 0) return atid; @@ -149,8 +162,21 @@ static struct attribute *coresight_tnoc_attrs[] = { NULL, }; +static umode_t trace_id_is_visible(struct kobject *kobj, + struct attribute *attr, int idx) +{ + struct device *dev = kobj_to_dev(kobj); + struct trace_noc_drvdata *drvdata = dev_get_drvdata(dev->parent); + + if (attr == &dev_attr_traceid.attr && drvdata->atid < 0) + return 0; + + return attr->mode; +} + static const struct attribute_group coresight_tnoc_group = { .attrs = coresight_tnoc_attrs, + .is_visible = trace_id_is_visible, }; static const struct attribute_group *coresight_tnoc_groups[] = { @@ -158,9 +184,8 @@ static const struct attribute_group *coresight_tnoc_groups[] = { NULL, }; -static int trace_noc_probe(struct amba_device *adev, const struct amba_id *id) +static int _tnoc_probe(struct device *dev, struct resource *res) { - struct device *dev = &adev->dev; struct coresight_platform_data *pdata; struct trace_noc_drvdata *drvdata; struct coresight_desc desc = { 0 }; @@ -173,16 +198,20 @@ static int trace_noc_probe(struct amba_device *adev, const struct amba_id *id) pdata = coresight_get_platform_data(dev); if (IS_ERR(pdata)) return PTR_ERR(pdata); - adev->dev.platform_data = pdata; + dev->platform_data = pdata; drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); if (!drvdata) return -ENOMEM; - drvdata->dev = &adev->dev; + drvdata->dev = dev; dev_set_drvdata(dev, drvdata); - drvdata->base = devm_ioremap_resource(dev, &adev->res); + ret = coresight_get_enable_clocks(dev, &drvdata->pclk, NULL); + if (ret) + return ret; + + drvdata->base = devm_ioremap_resource(dev, res); if (IS_ERR(drvdata->base)) return PTR_ERR(drvdata->base); @@ -195,20 +224,31 @@ static int trace_noc_probe(struct amba_device *adev, const struct amba_id *id) desc.ops = &trace_noc_cs_ops; desc.type = CORESIGHT_DEV_TYPE_LINK; desc.subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_MERG; - desc.pdata = adev->dev.platform_data; - desc.dev = &adev->dev; + desc.pdata = pdata; + desc.dev = dev; desc.access = CSDEV_ACCESS_IOMEM(drvdata->base); desc.groups = coresight_tnoc_groups; drvdata->csdev = coresight_register(&desc); if (IS_ERR(drvdata->csdev)) { - coresight_trace_id_put_system_id(drvdata->atid); + if (drvdata->atid > 0) + coresight_trace_id_put_system_id(drvdata->atid); return PTR_ERR(drvdata->csdev); } - pm_runtime_put(&adev->dev); return 0; } +static int trace_noc_probe(struct amba_device *adev, const struct amba_id *id) +{ + int ret; + + ret = _tnoc_probe(&adev->dev, &adev->res); + if (!ret) + pm_runtime_put(&adev->dev); + + return ret; +} + static void trace_noc_remove(struct amba_device *adev) { struct trace_noc_drvdata *drvdata = dev_get_drvdata(&adev->dev); @@ -240,7 +280,58 @@ static struct amba_driver trace_noc_driver = { .id_table = trace_noc_ids, }; -module_amba_driver(trace_noc_driver); +static int itnoc_probe(struct platform_device *pdev) +{ + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + int ret; + + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + ret = _tnoc_probe(&pdev->dev, res); + pm_runtime_put(&pdev->dev); + if (ret) + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static void itnoc_remove(struct platform_device *pdev) +{ + struct trace_noc_drvdata *drvdata = platform_get_drvdata(pdev); + + coresight_unregister(drvdata->csdev); + pm_runtime_disable(&pdev->dev); +} + +static const struct of_device_id itnoc_of_match[] = { + { .compatible = "qcom,coresight-itnoc" }, + {} +}; +MODULE_DEVICE_TABLE(of, itnoc_of_match); + +static struct platform_driver itnoc_driver = { + .probe = itnoc_probe, + .remove = itnoc_remove, + .driver = { + .name = "coresight-itnoc", + .of_match_table = itnoc_of_match, + .suppress_bind_attrs = true, + }, +}; + +static int __init tnoc_init(void) +{ + return coresight_init_driver("tnoc", &trace_noc_driver, &itnoc_driver, THIS_MODULE); +} + +static void __exit tnoc_exit(void) +{ + coresight_remove_driver(&trace_noc_driver, &itnoc_driver); +} +module_init(tnoc_init); +module_exit(tnoc_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Trace NOC driver"); From 4d9024d14d1bacb097f33ef7d3ed1696d6a46c88 Mon Sep 17 00:00:00 2001 From: Yuanfang Zhang Date: Wed, 3 Dec 2025 20:43:09 -0800 Subject: [PATCH 180/297] coresight-tnoc: Add runtime PM support for Interconnect TNOC This patch adds runtime power management support for platform-based CoreSight Interconnect TNOC (ITNOC) devices. It introduces suspend and resume callbacks to manage the APB clock (`pclk`) during device runtime transitions. Signed-off-by: Yuanfang Zhang Reviewed-by: Leo Yan Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20251203-itnoc-v5-3-5b97c63f2268@oss.qualcomm.com --- drivers/hwtracing/coresight/coresight-tnoc.c | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/drivers/hwtracing/coresight/coresight-tnoc.c b/drivers/hwtracing/coresight/coresight-tnoc.c index affd8204b534..1128612e70a7 100644 --- a/drivers/hwtracing/coresight/coresight-tnoc.c +++ b/drivers/hwtracing/coresight/coresight-tnoc.c @@ -305,6 +305,28 @@ static void itnoc_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); } +#ifdef CONFIG_PM +static int itnoc_runtime_suspend(struct device *dev) +{ + struct trace_noc_drvdata *drvdata = dev_get_drvdata(dev); + + clk_disable_unprepare(drvdata->pclk); + + return 0; +} + +static int itnoc_runtime_resume(struct device *dev) +{ + struct trace_noc_drvdata *drvdata = dev_get_drvdata(dev); + + return clk_prepare_enable(drvdata->pclk); +} +#endif + +static const struct dev_pm_ops itnoc_dev_pm_ops = { + SET_RUNTIME_PM_OPS(itnoc_runtime_suspend, itnoc_runtime_resume, NULL) +}; + static const struct of_device_id itnoc_of_match[] = { { .compatible = "qcom,coresight-itnoc" }, {} @@ -318,6 +340,7 @@ static struct platform_driver itnoc_driver = { .name = "coresight-itnoc", .of_match_table = itnoc_of_match, .suppress_bind_attrs = true, + .pm = &itnoc_dev_pm_ops, }, }; From 3dc4092fe5c8baf6bf4e882b44615f19564a5076 Mon Sep 17 00:00:00 2001 From: Raviteja Laggyshetty Date: Tue, 20 Jan 2026 09:30:10 +0000 Subject: [PATCH 181/297] interconnect: qcom: qcs8300: fix the num_links for nsp icc node The qxm_nsp node is configured with an incorrect num_links value, causing remoteproc driver to fail probing because it cannot acquire the interconnect path for qxm_nsp -> ebi. This results in the following error in dmesg: platform 26300000.remoteproc: deferred probe pending: qcom_q6v5_pas: failed to acquire interconnect path Set num_links to 2 to match the two link_nodes, allowing remoteproc clients to obtain the correct path handle and vote on qxm_nsp -> ebi. Fixes: 874be3339c85 ("interconnect: qcom: qcs8300: convert to dynamic IDs") Signed-off-by: Raviteja Laggyshetty Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20260120-monaco_num_links_fix_nsp_ebi_path-v3-1-536be21ce3ff@oss.qualcomm.com Signed-off-by: Georgi Djakov --- drivers/interconnect/qcom/qcs8300.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/interconnect/qcom/qcs8300.c b/drivers/interconnect/qcom/qcs8300.c index 70a377bbcf29..bc403a9bf68c 100644 --- a/drivers/interconnect/qcom/qcs8300.c +++ b/drivers/interconnect/qcom/qcs8300.c @@ -629,7 +629,7 @@ static struct qcom_icc_node qxm_nsp = { .name = "qxm_nsp", .channels = 2, .buswidth = 32, - .num_links = 1, + .num_links = 2, .link_nodes = { &qns_hcp, &qns_nsp_gemnoc }, }; From cd796ca8b83be6bcab7610e420078539fe67ea03 Mon Sep 17 00:00:00 2001 From: Pragnesh Papaniya Date: Tue, 20 Jan 2026 19:07:35 +0530 Subject: [PATCH 182/297] dt-bindings: interconnect: qcom-bwmon: Document Glymur BWMONs Document Glymur BWMONs, which has multiple (one per cluster) BWMONv4 instances for the CPU->DDR path. Signed-off-by: Pragnesh Papaniya Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260120-glymur_bwmon_binding-v1-1-57848445eccf@oss.qualcomm.com Signed-off-by: Georgi Djakov --- .../devicetree/bindings/interconnect/qcom,msm8998-bwmon.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/interconnect/qcom,msm8998-bwmon.yaml b/Documentation/devicetree/bindings/interconnect/qcom,msm8998-bwmon.yaml index 17b09292000e..ce79521bb1ef 100644 --- a/Documentation/devicetree/bindings/interconnect/qcom,msm8998-bwmon.yaml +++ b/Documentation/devicetree/bindings/interconnect/qcom,msm8998-bwmon.yaml @@ -25,6 +25,7 @@ properties: - const: qcom,msm8998-bwmon # BWMON v4 - items: - enum: + - qcom,glymur-cpu-bwmon - qcom,kaanapali-cpu-bwmon - qcom,qcm2290-cpu-bwmon - qcom,qcs615-cpu-bwmon From 5da8c55dd879347db64a1d28e66f6d7f62652373 Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Wed, 21 Jan 2026 18:15:41 +0800 Subject: [PATCH 183/297] coresight: tmc: Add missing doc including reading and etr_mode of struct tmc_drvdata tmc_drvdata::reading is used to indicate whether a reading process is performed through /dev/xyz.tmc. tmc_drvdata::etr_mode is used to store the Coresight TMC-ETR buffer mode selected by the user. Document them. Reviewed-by: James Clark Signed-off-by: Yicong Yang Reviewed-by: Leo Yan Signed-off-by: Junhao He Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20260121101543.2017014-2-wangyushan12@huawei.com --- drivers/hwtracing/coresight/coresight-tmc.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hwtracing/coresight/coresight-tmc.h b/drivers/hwtracing/coresight/coresight-tmc.h index 95473d131032..319a354ede9f 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.h +++ b/drivers/hwtracing/coresight/coresight-tmc.h @@ -221,6 +221,7 @@ struct tmc_resrv_buf { * @pid: Process ID of the process that owns the session that is using * this component. For example this would be the pid of the Perf * process. + * @reading: buffer's in the reading through "/dev/xyz.tmc" entry * @stop_on_flush: Stop on flush trigger user configuration. * @buf: Snapshot of the trace data for ETF/ETB. * @etr_buf: details of buffer used in TMC-ETR @@ -233,6 +234,7 @@ struct tmc_resrv_buf { * @trigger_cntr: amount of words to store after a trigger. * @etr_caps: Bitmask of capabilities of the TMC ETR, inferred from the * device configuration register (DEVID) + * @etr_mode: User preferred mode of the ETR device, default auto mode. * @idr: Holds etr_bufs allocated for this ETR. * @idr_mutex: Access serialisation for idr. * @sysfs_buf: SYSFS buffer for ETR. From e6e43e82c79c97917cbe356c07e8a6f3f982ab53 Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Wed, 21 Jan 2026 18:15:42 +0800 Subject: [PATCH 184/297] coresight: tmc-etr: Fix race condition between sysfs and perf mode When trying to run perf and sysfs mode simultaneously, the WARN_ON() in tmc_etr_enable_hw() is triggered sometimes: WARNING: CPU: 42 PID: 3911571 at drivers/hwtracing/coresight/coresight-tmc-etr.c:1060 tmc_etr_enable_hw+0xc0/0xd8 [coresight_tmc] [..snip..] Call trace: tmc_etr_enable_hw+0xc0/0xd8 [coresight_tmc] (P) tmc_enable_etr_sink+0x11c/0x250 [coresight_tmc] (L) tmc_enable_etr_sink+0x11c/0x250 [coresight_tmc] coresight_enable_path+0x1c8/0x218 [coresight] coresight_enable_sysfs+0xa4/0x228 [coresight] enable_source_store+0x58/0xa8 [coresight] dev_attr_store+0x20/0x40 sysfs_kf_write+0x4c/0x68 kernfs_fop_write_iter+0x120/0x1b8 vfs_write+0x2c8/0x388 ksys_write+0x74/0x108 __arm64_sys_write+0x24/0x38 el0_svc_common.constprop.0+0x64/0x148 do_el0_svc+0x24/0x38 el0_svc+0x3c/0x130 el0t_64_sync_handler+0xc8/0xd0 el0t_64_sync+0x1ac/0x1b0 ---[ end trace 0000000000000000 ]--- Since the enablement of sysfs mode is separeted into two critical regions, one for sysfs buffer allocation and another for hardware enablement, it's possible to race with the perf mode. Fix this by double check whether the perf mode's been used before enabling the hardware in sysfs mode. mode: [sysfs mode] [perf mode] tmc_etr_get_sysfs_buffer() spin_lock(&drvdata->spinlock) [sysfs buffer allocation] spin_unlock(&drvdata->spinlock) spin_lock(&drvdata->spinlock) tmc_etr_enable_hw() drvdata->etr_buf = etr_perf->etr_buf spin_unlock(&drvdata->spinlock) spin_lock(&drvdata->spinlock) tmc_etr_enable_hw() WARN_ON(drvdata->etr_buf) // WARN sicne etr_buf initialized at the perf side spin_unlock(&drvdata->spinlock) With this fix, we retain the check for CS_MODE_PERF in get_etr_sysfs_buf. This ensures we verify whether the perf mode's already running before we actually allocate the buffer. Then we can save the time of allocating/freeing the sysfs buffer if race with the perf mode. Fixes: 296b01fd106e ("coresight: Refactor out buffer allocation function for ETR") Signed-off-by: Yicong Yang Signed-off-by: Junhao He Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20260121101543.2017014-3-wangyushan12@huawei.com --- drivers/hwtracing/coresight/coresight-tmc-etr.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index e0d83ee01b77..fc0a946053dd 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -1306,6 +1306,19 @@ static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev) raw_spin_lock_irqsave(&drvdata->spinlock, flags); + /* + * Since the sysfs buffer allocation and the hardware enablement is not + * in the same critical region, it's possible to race with the perf. + */ + if (coresight_get_mode(csdev) == CS_MODE_PERF) { + drvdata->sysfs_buf = NULL; + raw_spin_unlock_irqrestore(&drvdata->spinlock, flags); + + /* Free allocated memory out side of the spinlock */ + tmc_etr_free_sysfs_buf(sysfs_buf); + return -EBUSY; + } + /* * In sysFS mode we can have multiple writers per sink. Since this * sink is already enabled no memory is needed and the HW need not be From eebe8dbd8630f51cf70b1f68a440cd3d7f7a914d Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Wed, 21 Jan 2026 18:15:43 +0800 Subject: [PATCH 185/297] coresight: tmc: Decouple the perf buffer allocation from sysfs mode Currently the perf buffer allocation follows the below logic: - if the required AUX buffer size if larger, allocate the buffer with the required size - otherwise allocate the size reference to the sysfs buffer size This is not useful as we only collect to one AUX data, so just try to allocate the buffer match the AUX buffer size. Suggested-by: Suzuki K Poulose Link: https://lore.kernel.org/linux-arm-kernel/df8967cd-2157-46a2-97d9-a1aea883cf63@arm.com/ Signed-off-by: Yicong Yang Signed-off-by: Junhao He Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20260121101543.2017014-4-wangyushan12@huawei.com --- .../hwtracing/coresight/coresight-tmc-etr.c | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index fc0a946053dd..cee82e52c4ea 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -1367,9 +1367,7 @@ EXPORT_SYMBOL_GPL(tmc_etr_get_buffer); /* * alloc_etr_buf: Allocate ETR buffer for use by perf. - * The size of the hardware buffer is dependent on the size configured - * via sysfs and the perf ring buffer size. We prefer to allocate the - * largest possible size, scaling down the size by half until it + * Allocate the largest possible size, scaling down the size by half until it * reaches a minimum limit (1M), beyond which we give up. */ static struct etr_buf * @@ -1378,36 +1376,26 @@ alloc_etr_buf(struct tmc_drvdata *drvdata, struct perf_event *event, { int node; struct etr_buf *etr_buf; - unsigned long size; + ssize_t size; node = (event->cpu == -1) ? NUMA_NO_NODE : cpu_to_node(event->cpu); - /* - * Try to match the perf ring buffer size if it is larger - * than the size requested via sysfs. - */ - if ((nr_pages << PAGE_SHIFT) > drvdata->size) { - etr_buf = tmc_alloc_etr_buf(drvdata, ((ssize_t)nr_pages << PAGE_SHIFT), - 0, node, NULL); - if (!IS_ERR(etr_buf)) - goto done; - } + + /* Use the minimum limit if the required size is smaller */ + size = nr_pages << PAGE_SHIFT; + size = max_t(ssize_t, size, TMC_ETR_PERF_MIN_BUF_SIZE); /* - * Else switch to configured size for this ETR - * and scale down until we hit the minimum limit. + * Try to allocate the required size for this ETR, if failed scale + * down until we hit the minimum limit. */ - size = drvdata->size; do { etr_buf = tmc_alloc_etr_buf(drvdata, size, 0, node, NULL); if (!IS_ERR(etr_buf)) - goto done; + return etr_buf; size /= 2; } while (size >= TMC_ETR_PERF_MIN_BUF_SIZE); return ERR_PTR(-ENOMEM); - -done: - return etr_buf; } static struct etr_buf * From ff112f1ecd10b72004eac05bae395e1c65f0c63c Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 21 Jan 2026 15:49:31 +0100 Subject: [PATCH 186/297] Revert "mmc: rtsx_pci_sdmmc: increase power-on settling delay to 5ms" This reverts commit aced969e9bf3701dc75cfca57c78c031b7875b9d. It was determined that this was not the correct "fix", so should be reverted. Fixes: aced969e9bf3 ("mmc: rtsx_pci_sdmmc: increase power-on settling delay to 5ms") Cc: Matthew Schwartz Cc: Ulf Hansson Signed-off-by: Greg Kroah-Hartman --- drivers/mmc/host/rtsx_pci_sdmmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/rtsx_pci_sdmmc.c b/drivers/mmc/host/rtsx_pci_sdmmc.c index 767816c09491..b847d79d4d8c 100644 --- a/drivers/mmc/host/rtsx_pci_sdmmc.c +++ b/drivers/mmc/host/rtsx_pci_sdmmc.c @@ -947,7 +947,7 @@ static int sd_power_on(struct realtek_pci_sdmmc *host, unsigned char power_mode) if (err < 0) return err; - mdelay(5); + mdelay(1); err = rtsx_pci_write_register(pcr, CARD_OE, SD_OUTPUT_EN, SD_OUTPUT_EN); if (err < 0) From c23f0550c05d40762b141808709667759291c938 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 21 Jan 2026 15:50:36 +0100 Subject: [PATCH 187/297] Revert "mmc: rtsx: reset power state on suspend" This reverts commit eac85fbd0867c25ac517f58fae401d65c627edff. This is not the correct change, so revert it for now. Fixes: eac85fbd0867 ("mmc: rtsx: reset power state on suspend") Cc: Matthew Schwartz Cc: Ulf Hansson Signed-off-by: Greg Kroah-Hartman --- drivers/misc/cardreader/rtsx_pcr.c | 9 --------- drivers/mmc/host/rtsx_pci_sdmmc.c | 22 ---------------------- include/linux/rtsx_common.h | 1 - 3 files changed, 32 deletions(-) diff --git a/drivers/misc/cardreader/rtsx_pcr.c b/drivers/misc/cardreader/rtsx_pcr.c index f1f4d8ed544d..f9952d76d6ed 100644 --- a/drivers/misc/cardreader/rtsx_pcr.c +++ b/drivers/misc/cardreader/rtsx_pcr.c @@ -1654,7 +1654,6 @@ static int __maybe_unused rtsx_pci_suspend(struct device *dev_d) struct pci_dev *pcidev = to_pci_dev(dev_d); struct pcr_handle *handle = pci_get_drvdata(pcidev); struct rtsx_pcr *pcr = handle->pcr; - struct rtsx_slot *slot = &pcr->slots[RTSX_SD_CARD]; dev_dbg(&(pcidev->dev), "--> %s\n", __func__); @@ -1662,9 +1661,6 @@ static int __maybe_unused rtsx_pci_suspend(struct device *dev_d) mutex_lock(&pcr->pcr_mutex); - if (slot->p_dev && slot->power_off) - slot->power_off(slot->p_dev); - rtsx_pci_power_off(pcr, HOST_ENTER_S3, false); mutex_unlock(&pcr->pcr_mutex); @@ -1776,17 +1772,12 @@ static int rtsx_pci_runtime_suspend(struct device *device) struct pci_dev *pcidev = to_pci_dev(device); struct pcr_handle *handle = pci_get_drvdata(pcidev); struct rtsx_pcr *pcr = handle->pcr; - struct rtsx_slot *slot = &pcr->slots[RTSX_SD_CARD]; dev_dbg(device, "--> %s\n", __func__); cancel_delayed_work_sync(&pcr->carddet_work); mutex_lock(&pcr->pcr_mutex); - - if (slot->p_dev && slot->power_off) - slot->power_off(slot->p_dev); - rtsx_pci_power_off(pcr, HOST_ENTER_S3, true); mutex_unlock(&pcr->pcr_mutex); diff --git a/drivers/mmc/host/rtsx_pci_sdmmc.c b/drivers/mmc/host/rtsx_pci_sdmmc.c index b847d79d4d8c..5d3599ee06bf 100644 --- a/drivers/mmc/host/rtsx_pci_sdmmc.c +++ b/drivers/mmc/host/rtsx_pci_sdmmc.c @@ -47,7 +47,6 @@ struct realtek_pci_sdmmc { }; static int sdmmc_init_sd_express(struct mmc_host *mmc, struct mmc_ios *ios); -static int sd_power_on(struct realtek_pci_sdmmc *host, unsigned char power_mode); static inline struct device *sdmmc_dev(struct realtek_pci_sdmmc *host) { @@ -822,15 +821,6 @@ static void sd_request(struct work_struct *work) rtsx_pci_start_run(pcr); - if (host->prev_power_state == MMC_POWER_OFF) { - err = sd_power_on(host, MMC_POWER_ON); - if (err) { - cmd->error = err; - mutex_unlock(&pcr->pcr_mutex); - goto finish; - } - } - rtsx_pci_switch_clock(pcr, host->clock, host->ssc_depth, host->initial_mode, host->double_clk, host->vpclk); rtsx_pci_write_register(pcr, CARD_SELECT, 0x07, SD_MOD_SEL); @@ -1491,16 +1481,6 @@ static void rtsx_pci_sdmmc_card_event(struct platform_device *pdev) mmc_detect_change(host->mmc, 0); } -static void rtsx_pci_sdmmc_power_off(struct platform_device *pdev) -{ - struct realtek_pci_sdmmc *host = platform_get_drvdata(pdev); - - if (!host) - return; - - host->prev_power_state = MMC_POWER_OFF; -} - static int rtsx_pci_sdmmc_drv_probe(struct platform_device *pdev) { struct mmc_host *mmc; @@ -1533,7 +1513,6 @@ static int rtsx_pci_sdmmc_drv_probe(struct platform_device *pdev) platform_set_drvdata(pdev, host); pcr->slots[RTSX_SD_CARD].p_dev = pdev; pcr->slots[RTSX_SD_CARD].card_event = rtsx_pci_sdmmc_card_event; - pcr->slots[RTSX_SD_CARD].power_off = rtsx_pci_sdmmc_power_off; mutex_init(&host->host_mutex); @@ -1565,7 +1544,6 @@ static void rtsx_pci_sdmmc_drv_remove(struct platform_device *pdev) pcr = host->pcr; pcr->slots[RTSX_SD_CARD].p_dev = NULL; pcr->slots[RTSX_SD_CARD].card_event = NULL; - pcr->slots[RTSX_SD_CARD].power_off = NULL; mmc = host->mmc; cancel_work_sync(&host->work); diff --git a/include/linux/rtsx_common.h b/include/linux/rtsx_common.h index f294f478f0c0..da9c8c6b5d50 100644 --- a/include/linux/rtsx_common.h +++ b/include/linux/rtsx_common.h @@ -32,7 +32,6 @@ struct platform_device; struct rtsx_slot { struct platform_device *p_dev; void (*card_event)(struct platform_device *p_dev); - void (*power_off)(struct platform_device *p_dev); }; #endif From eb89b17f283b233ba721fce358fa0d15223ae69d Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 21 Jan 2026 15:51:04 +0100 Subject: [PATCH 188/297] Revert "mmc: rtsx_pci: add quirk to disable MMC_CAP_AGGRESSIVE_PM for RTS525A" This reverts commit 5f0bf80cc5e04d31eeb201683e0b477c24bd18e7. This was asked to be reverted as it is not the correct way to do this. Fixes: 5f0bf80cc5e0 ("mmc: rtsx_pci: add quirk to disable MMC_CAP_AGGRESSIVE_PM for RTS525A") Cc: Matthew Schwartz Signed-off-by: Greg Kroah-Hartman --- drivers/misc/cardreader/rts5249.c | 3 --- drivers/mmc/host/rtsx_pci_sdmmc.c | 4 ++-- include/linux/rtsx_pci.h | 1 - 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/misc/cardreader/rts5249.c b/drivers/misc/cardreader/rts5249.c index 87d576a03e68..38aefd8db452 100644 --- a/drivers/misc/cardreader/rts5249.c +++ b/drivers/misc/cardreader/rts5249.c @@ -78,9 +78,6 @@ static void rtsx_base_fetch_vendor_settings(struct rtsx_pcr *pcr) if (CHK_PCI_PID(pcr, PID_524A) || CHK_PCI_PID(pcr, PID_525A)) pcr->rtd3_en = rtsx_reg_to_rtd3_uhsii(reg); - if (CHK_PCI_PID(pcr, PID_525A)) - pcr->extra_caps |= EXTRA_CAPS_NO_AGGRESSIVE_PM; - if (rtsx_check_mmc_support(reg)) pcr->extra_caps |= EXTRA_CAPS_NO_MMC; pcr->sd30_drive_sel_3v3 = rtsx_reg_to_sd30_drive_sel_3v3(reg); diff --git a/drivers/mmc/host/rtsx_pci_sdmmc.c b/drivers/mmc/host/rtsx_pci_sdmmc.c index 5d3599ee06bf..dc2587ff8519 100644 --- a/drivers/mmc/host/rtsx_pci_sdmmc.c +++ b/drivers/mmc/host/rtsx_pci_sdmmc.c @@ -1456,8 +1456,8 @@ static void realtek_init_host(struct realtek_pci_sdmmc *host) mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED | MMC_CAP_BUS_WIDTH_TEST | MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25; - if (pcr->rtd3_en && !(pcr->extra_caps & EXTRA_CAPS_NO_AGGRESSIVE_PM)) - mmc->caps |= MMC_CAP_AGGRESSIVE_PM; + if (pcr->rtd3_en) + mmc->caps = mmc->caps | MMC_CAP_AGGRESSIVE_PM; mmc->caps2 = MMC_CAP2_NO_PRESCAN_POWERUP | MMC_CAP2_FULL_PWR_CYCLE | MMC_CAP2_NO_SDIO; mmc->max_current_330 = 400; diff --git a/include/linux/rtsx_pci.h b/include/linux/rtsx_pci.h index f6122349c00e..3c5689356004 100644 --- a/include/linux/rtsx_pci.h +++ b/include/linux/rtsx_pci.h @@ -1230,7 +1230,6 @@ struct rtsx_pcr { #define EXTRA_CAPS_MMC_8BIT (1 << 5) #define EXTRA_CAPS_NO_MMC (1 << 7) #define EXTRA_CAPS_SD_EXPRESS (1 << 8) -#define EXTRA_CAPS_NO_AGGRESSIVE_PM (1 << 9) u32 extra_caps; #define IC_VER_A 0 From ae801944cbfb70326afc373c11a282d1ce3bae97 Mon Sep 17 00:00:00 2001 From: Mukesh Kumar Sisodiya Date: Fri, 26 Dec 2025 15:04:08 +0000 Subject: [PATCH 189/297] fpga: dfl: fix typo in header file fix typo "definitons" ==> "definitions" in DFHv1 Register Offset comment. Signed-off-by: Mukesh Kumar Sisodiya Link: https://lore.kernel.org/r/20251226150408.2802-1-mukeshkumarsisodiya1981@gmail.com Reviewed-by: Xu Yilun Signed-off-by: Xu Yilun --- drivers/fpga/dfl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/fpga/dfl.h b/drivers/fpga/dfl.h index 95539f1213cb..9549f63b00eb 100644 --- a/drivers/fpga/dfl.h +++ b/drivers/fpga/dfl.h @@ -82,7 +82,7 @@ #define DFH_TYPE_FIU 4 /* - * DFHv1 Register Offset definitons + * DFHv1 Register Offset definitions * In DHFv1, DFH + GUID + CSR_START + CSR_SIZE_GROUP + PARAM_HDR + PARAM_DATA * as common header registers */ From 35bf070bb53c88794772336da759b55447106de4 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Thu, 22 Jan 2026 20:34:22 +0100 Subject: [PATCH 190/297] interconnect: qcom: smd-rpm: drop duplicated QCOM_RPM_SMD_KEY_RATE define The 'linux/soc/qcom/smd-rpm.h' header defines QCOM_RPM_SMD_KEY_RATE with the exact same value. $ git grep -nHE 'define[[:blank:]]+QCOM_RPM_SMD_KEY_RATE[[:blank:]]' drivers/interconnect/qcom/smd-rpm.c:17:#define QCOM_RPM_SMD_KEY_RATE 0x007a484b include/linux/soc/qcom/smd-rpm.h:52:#define QCOM_RPM_SMD_KEY_RATE 0x007a484b Drop the local define to avoid the duplication. No functional changes intended. Compile tested only. Signed-off-by: Gabor Juhos Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20260122-icc-qcom-dupe-defines-v1-1-eea876c2d98f@gmail.com Signed-off-by: Georgi Djakov --- drivers/interconnect/qcom/smd-rpm.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/interconnect/qcom/smd-rpm.c b/drivers/interconnect/qcom/smd-rpm.c index 8316c87a2c60..dbc7ae50b02b 100644 --- a/drivers/interconnect/qcom/smd-rpm.c +++ b/drivers/interconnect/qcom/smd-rpm.c @@ -14,7 +14,6 @@ #include "icc-rpm.h" #define RPM_KEY_BW 0x00007762 -#define QCOM_RPM_SMD_KEY_RATE 0x007a484b static struct qcom_smd_rpm *icc_smd_rpm; From 0e841d1d561376828ea9ecdf7d591f491046924c Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Thu, 22 Jan 2026 20:34:23 +0100 Subject: [PATCH 191/297] interconnect: qcom: msm8974: drop duplicated RPM_BUS_{MASTER,SLAVE}_REQ defines Both the RPM_BUS_MASTER_REQ and the RPM_BUS_SLAVE_REQ constants are also defined in the 'icc-rpm.h' header. $ git grep -nHE 'define[[:blank:]]+RPM_BUS_MASTER_REQ[[:blank:]]' drivers/interconnect/qcom/icc-rpm.h:16:#define RPM_BUS_MASTER_REQ 0x73616d62 drivers/interconnect/qcom/msm8974.c:176:#define RPM_BUS_MASTER_REQ 0x73616d62 $ git grep -nHE 'define[[:blank:]]+RPM_BUS_SLAVE_REQ[[:blank:]]' drivers/interconnect/qcom/icc-rpm.h:17:#define RPM_BUS_SLAVE_REQ 0x766c7362 drivers/interconnect/qcom/msm8974.c:177:#define RPM_BUS_SLAVE_REQ 0x766c7362 Drop the local defines to avoid the duplications. No functional changes intended. Compile tested only. Signed-off-by: Gabor Juhos Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20260122-icc-qcom-dupe-defines-v1-2-eea876c2d98f@gmail.com Signed-off-by: Georgi Djakov --- drivers/interconnect/qcom/msm8974.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/interconnect/qcom/msm8974.c b/drivers/interconnect/qcom/msm8974.c index 469fc48ebfe9..3239edc37f02 100644 --- a/drivers/interconnect/qcom/msm8974.c +++ b/drivers/interconnect/qcom/msm8974.c @@ -173,9 +173,6 @@ enum { MSM8974_SNOC_SLV_QDSS_STM, }; -#define RPM_BUS_MASTER_REQ 0x73616d62 -#define RPM_BUS_SLAVE_REQ 0x766c7362 - #define to_msm8974_icc_provider(_provider) \ container_of(_provider, struct msm8974_icc_provider, provider) From a02d46be8aec5e3daea8033446f93ea4bab1ed62 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Mon, 19 Jan 2026 21:28:04 +0000 Subject: [PATCH 192/297] iio: adc: ad4062: Switch from struct i3c_priv_xfer to struct i3c_xfer Commit 9904232ae30bc ("i3c: drop i3c_priv_xfer and i3c_device_do_priv_xfers()") currently in the i3c/for-next tree removes the deprecated struct i3c_priv_xfer and i3c_device_do_priv_xfers(). Switch to struct i3c_xfer and i3c_device_do_xfers(..., I3C_SDR) now rather causing a build issue when both trees are merged. Suggested-by: Sasha Levin Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad4062.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/iio/adc/ad4062.c b/drivers/iio/adc/ad4062.c index a6b3ccc98acf..dd4ad32aa6f5 100644 --- a/drivers/iio/adc/ad4062.c +++ b/drivers/iio/adc/ad4062.c @@ -468,13 +468,13 @@ static int ad4062_set_operation_mode(struct ad4062_state *st, if (mode == AD4062_MONITOR_MODE) { /* Change address pointer to enter monitor mode */ - struct i3c_priv_xfer xfer_trigger = { + struct i3c_xfer xfer_trigger = { .data.out = &st->conv_addr, .len = sizeof(st->conv_addr), .rnw = false, }; st->conv_addr = AD4062_REG_CONV_TRIGGER_32BITS; - return i3c_device_do_priv_xfers(st->i3cdev, &xfer_trigger, 1); + return i3c_device_do_xfers(st->i3cdev, &xfer_trigger, 1, I3C_SDR); } return regmap_write(st->regmap, AD4062_REG_MODE_SET, @@ -607,18 +607,18 @@ static void ad4062_trigger_work(struct work_struct *work) * Read current conversion, if at reg CONV_READ, stop bit triggers * next sample and does not need writing the address. */ - struct i3c_priv_xfer xfer_sample = { + struct i3c_xfer xfer_sample = { .data.in = &st->buf.be32, .len = st->conv_sizeof, .rnw = true, }; - struct i3c_priv_xfer xfer_trigger = { + struct i3c_xfer xfer_trigger = { .data.out = &st->conv_addr, .len = sizeof(st->conv_addr), .rnw = false, }; - ret = i3c_device_do_priv_xfers(st->i3cdev, &xfer_sample, 1); + ret = i3c_device_do_xfers(st->i3cdev, &xfer_sample, 1, I3C_SDR); if (ret) return; @@ -627,7 +627,7 @@ static void ad4062_trigger_work(struct work_struct *work) if (st->gpo_irq[1]) return; - i3c_device_do_priv_xfers(st->i3cdev, &xfer_trigger, 1); + i3c_device_do_xfers(st->i3cdev, &xfer_trigger, 1, I3C_SDR); } static irqreturn_t ad4062_poll_handler(int irq, void *p) @@ -852,12 +852,12 @@ static int ad4062_set_chan_calibscale(struct ad4062_state *st, int gain_int, static int ad4062_read_chan_raw(struct ad4062_state *st, int *val) { struct i3c_device *i3cdev = st->i3cdev; - struct i3c_priv_xfer xfer_trigger = { + struct i3c_xfer xfer_trigger = { .data.out = &st->conv_addr, .len = sizeof(st->conv_addr), .rnw = false, }; - struct i3c_priv_xfer xfer_sample = { + struct i3c_xfer xfer_sample = { .data.in = &st->buf.be32, .len = sizeof(st->buf.be32), .rnw = true, @@ -876,7 +876,7 @@ static int ad4062_read_chan_raw(struct ad4062_state *st, int *val) reinit_completion(&st->completion); /* Change address pointer to trigger conversion */ st->conv_addr = AD4062_REG_CONV_TRIGGER_32BITS; - ret = i3c_device_do_priv_xfers(i3cdev, &xfer_trigger, 1); + ret = i3c_device_do_xfers(i3cdev, &xfer_trigger, 1, I3C_SDR); if (ret) return ret; /* @@ -888,7 +888,7 @@ static int ad4062_read_chan_raw(struct ad4062_state *st, int *val) if (!ret) return -ETIMEDOUT; - ret = i3c_device_do_priv_xfers(i3cdev, &xfer_sample, 1); + ret = i3c_device_do_xfers(i3cdev, &xfer_sample, 1, I3C_SDR); if (ret) return ret; *val = be32_to_cpu(st->buf.be32); @@ -1236,7 +1236,7 @@ static int pm_ad4062_triggered_buffer_postenable(struct ad4062_state *st) st->conv_sizeof = ad4062_sizeof_storagebits(st); st->conv_addr = ad4062_get_conv_addr(st, st->conv_sizeof); /* CONV_READ requires read to trigger first sample. */ - struct i3c_priv_xfer xfer_sample[2] = { + struct i3c_xfer xfer_sample[2] = { { .data.out = &st->conv_addr, .len = sizeof(st->conv_addr), @@ -1249,8 +1249,8 @@ static int pm_ad4062_triggered_buffer_postenable(struct ad4062_state *st) } }; - return i3c_device_do_priv_xfers(st->i3cdev, xfer_sample, - st->gpo_irq[1] ? 2 : 1); + return i3c_device_do_xfers(st->i3cdev, xfer_sample, + st->gpo_irq[1] ? 2 : 1, I3C_SDR); } static int ad4062_triggered_buffer_postenable(struct iio_dev *indio_dev) From e559c864146070905ce7bc6da1e9d269232d609b Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Mon, 19 Jan 2026 21:48:16 +0000 Subject: [PATCH 193/297] iio: magn: mmc5633: Ensure REGMAP_I2C / I3C not build if I2C / I3C is not. Fixes the following build warning (and equivalent I2C one) WARNING: unmet direct dependencies detected for REGMAP_I3C Depends on [n]: I3C [=n] Selected by [m]: - MMC5633 [=m] && IIO [=y] && (I2C [=y] || I3C [=n]) Fixes: 6e5f6bf2e3f0 ("iio: magnetometer: Add mmc5633 sensor") Reviewed-by: Frank Li Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/magnetometer/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig index cfb74a4a0836..2b81b22c9550 100644 --- a/drivers/iio/magnetometer/Kconfig +++ b/drivers/iio/magnetometer/Kconfig @@ -141,8 +141,8 @@ config MMC35240 config MMC5633 tristate "MEMSIC MMC5633 3-axis magnetic sensor" - select REGMAP_I2C - select REGMAP_I3C + select REGMAP_I2C if I2C + select REGMAP_I3C if I3C depends on I2C || I3C help Say yes here to build support for the MEMSIC MMC5633 3-axis From 88fd1f90792411226f49e5f285bf3faca5ac970b Mon Sep 17 00:00:00 2001 From: Kurt Borja Date: Tue, 20 Jan 2026 01:20:41 -0500 Subject: [PATCH 194/297] iio: core: Add and export __iio_dev_mode_lock() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add unconditional wrappers around the internal IIO mode lock. As mentioned in the documentation, this is not meant to be used by drivers, instead this will aid in the eventual addition of cleanup classes around conditional locks. Reviewed-by: David Lechner Reviewed-by: Nuno Sá Signed-off-by: Kurt Borja Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-core.c | 30 ++++++++++++++++++++++++++++++ include/linux/iio/iio.h | 3 +++ 2 files changed, 33 insertions(+) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index f69deefcfb6f..db803267df6e 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -2171,6 +2171,36 @@ int __devm_iio_device_register(struct device *dev, struct iio_dev *indio_dev, } EXPORT_SYMBOL_GPL(__devm_iio_device_register); +/** + * __iio_dev_mode_lock() - Locks the current IIO device mode + * @indio_dev: the iio_dev associated with the device + * + * If the device is either in direct or buffer mode, it's guaranteed to stay + * that way until __iio_dev_mode_unlock() is called. + * + * This function is not meant to be used directly by drivers to protect internal + * state; a driver should have it's own mechanisms for that matter. + * + * There are very few cases where a driver actually needs to lock the current + * mode unconditionally. It's recommended to use iio_device_claim_direct() or + * iio_device_claim_buffer_mode() pairs or related helpers instead. + */ +void __iio_dev_mode_lock(struct iio_dev *indio_dev) +{ + mutex_lock(&to_iio_dev_opaque(indio_dev)->mlock); +} +EXPORT_SYMBOL_GPL(__iio_dev_mode_lock); + +/** + * __iio_dev_mode_unlock() - Unlocks the current IIO device mode + * @indio_dev: the iio_dev associated with the device + */ +void __iio_dev_mode_unlock(struct iio_dev *indio_dev) +{ + mutex_unlock(&to_iio_dev_opaque(indio_dev)->mlock); +} +EXPORT_SYMBOL_GPL(__iio_dev_mode_unlock); + /** * __iio_device_claim_direct - Keep device in direct mode * @indio_dev: the iio_dev associated with the device diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h index 872ebdf0dd77..aecda887d833 100644 --- a/include/linux/iio/iio.h +++ b/include/linux/iio/iio.h @@ -661,6 +661,9 @@ void iio_device_unregister(struct iio_dev *indio_dev); int __devm_iio_device_register(struct device *dev, struct iio_dev *indio_dev, struct module *this_mod); int iio_push_event(struct iio_dev *indio_dev, u64 ev_code, s64 timestamp); + +void __iio_dev_mode_lock(struct iio_dev *indio_dev) __acquires(indio_dev); +void __iio_dev_mode_unlock(struct iio_dev *indio_dev) __releases(indio_dev); bool __iio_device_claim_direct(struct iio_dev *indio_dev); void __iio_device_release_direct(struct iio_dev *indio_dev); From c37ec9d507966a827913f42e06179e3475a00181 Mon Sep 17 00:00:00 2001 From: Kurt Borja Date: Tue, 20 Jan 2026 01:20:42 -0500 Subject: [PATCH 195/297] iio: core: Refactor iio_device_claim_direct() implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In order to eventually unify the locking API, implement iio_device_claim_direct() fully inline, with the use of __iio_dev_mode_lock(), which takes care of sparse annotations. Reviewed-by: David Lechner Reviewed-by: Nuno Sá Signed-off-by: Kurt Borja Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-core.c | 44 --------------------------------- include/linux/iio/iio.h | 40 +++++++++++++++++++++--------- 2 files changed, 28 insertions(+), 56 deletions(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index db803267df6e..0f8e3aa98b72 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -2201,50 +2201,6 @@ void __iio_dev_mode_unlock(struct iio_dev *indio_dev) } EXPORT_SYMBOL_GPL(__iio_dev_mode_unlock); -/** - * __iio_device_claim_direct - Keep device in direct mode - * @indio_dev: the iio_dev associated with the device - * - * If the device is in direct mode it is guaranteed to stay - * that way until __iio_device_release_direct() is called. - * - * Use with __iio_device_release_direct(). - * - * Drivers should only call iio_device_claim_direct(). - * - * Returns: true on success, false on failure. - */ -bool __iio_device_claim_direct(struct iio_dev *indio_dev) -{ - struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); - - mutex_lock(&iio_dev_opaque->mlock); - - if (iio_buffer_enabled(indio_dev)) { - mutex_unlock(&iio_dev_opaque->mlock); - return false; - } - return true; -} -EXPORT_SYMBOL_GPL(__iio_device_claim_direct); - -/** - * __iio_device_release_direct - releases claim on direct mode - * @indio_dev: the iio_dev associated with the device - * - * Release the claim. Device is no longer guaranteed to stay - * in direct mode. - * - * Drivers should only call iio_device_release_direct(). - * - * Use with __iio_device_claim_direct() - */ -void __iio_device_release_direct(struct iio_dev *indio_dev) -{ - mutex_unlock(&to_iio_dev_opaque(indio_dev)->mlock); -} -EXPORT_SYMBOL_GPL(__iio_device_release_direct); - /** * iio_device_claim_buffer_mode - Keep device in buffer mode * @indio_dev: the iio_dev associated with the device diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h index aecda887d833..e263ab5eeccf 100644 --- a/include/linux/iio/iio.h +++ b/include/linux/iio/iio.h @@ -664,31 +664,47 @@ int iio_push_event(struct iio_dev *indio_dev, u64 ev_code, s64 timestamp); void __iio_dev_mode_lock(struct iio_dev *indio_dev) __acquires(indio_dev); void __iio_dev_mode_unlock(struct iio_dev *indio_dev) __releases(indio_dev); -bool __iio_device_claim_direct(struct iio_dev *indio_dev); -void __iio_device_release_direct(struct iio_dev *indio_dev); /* * Helper functions that allow claim and release of direct mode * in a fashion that doesn't generate many false positives from sparse. * Note this must remain static inline in the header so that sparse - * can see the __acquire() marking. Revisit when sparse supports - * __cond_acquires() + * can see the __acquires() and __releases() annotations. + */ + +/** + * iio_device_claim_direct() - Keep device in direct mode + * @indio_dev: the iio_dev associated with the device + * + * If the device is in direct mode it is guaranteed to stay + * that way until iio_device_release_direct() is called. + * + * Use with iio_device_release_direct(). + * + * Returns: true on success, false on failure. */ static inline bool iio_device_claim_direct(struct iio_dev *indio_dev) { - if (!__iio_device_claim_direct(indio_dev)) - return false; + __iio_dev_mode_lock(indio_dev); - __acquire(iio_dev); + if (iio_buffer_enabled(indio_dev)) { + __iio_dev_mode_unlock(indio_dev); + return false; + } return true; } -static inline void iio_device_release_direct(struct iio_dev *indio_dev) -{ - __iio_device_release_direct(indio_dev); - __release(indio_dev); -} +/** + * iio_device_release_direct() - Releases claim on direct mode + * @indio_dev: the iio_dev associated with the device + * + * Release the claim. Device is no longer guaranteed to stay + * in direct mode. + * + * Use with iio_device_claim_direct(). + */ +#define iio_device_release_direct(indio_dev) __iio_dev_mode_unlock(indio_dev) int iio_device_claim_buffer_mode(struct iio_dev *indio_dev); void iio_device_release_buffer_mode(struct iio_dev *indio_dev); From 2daee817df13fb539be01a6a8094d52667d402f6 Mon Sep 17 00:00:00 2001 From: Kurt Borja Date: Tue, 20 Jan 2026 01:20:43 -0500 Subject: [PATCH 196/297] iio: core: Match iio_device_claim_*() semantics and implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement iio_device_claim_buffer_mode() fully inline with the use of __iio_dev_mode_lock(), which takes care of sparse annotations. To completely match iio_device_claim_direct() semantics, we need to also change iio_device_claim_buffer_mode() return semantics to usual true/false conditional lock semantics. Additionally, to avoid silently breaking out-of-tree drivers, rename iio_device_claim_buffer_mode() to iio_device_claim_try_buffer_mode(). Reviewed-by: David Lechner Reviewed-by: Nuno Sá Signed-off-by: Kurt Borja Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ade9000.c | 2 +- .../cros_ec_sensors/cros_ec_sensors_core.c | 5 +-- drivers/iio/health/max30100.c | 8 +--- drivers/iio/health/max30102.c | 2 +- drivers/iio/industrialio-core.c | 42 +------------------ drivers/iio/light/opt4060.c | 2 +- include/linux/iio/iio.h | 35 +++++++++++++++- 7 files changed, 39 insertions(+), 57 deletions(-) diff --git a/drivers/iio/adc/ade9000.c b/drivers/iio/adc/ade9000.c index 2de8a718d62a..db085dc5e526 100644 --- a/drivers/iio/adc/ade9000.c +++ b/drivers/iio/adc/ade9000.c @@ -964,7 +964,7 @@ static irqreturn_t ade9000_dready_thread(int irq, void *data) struct iio_dev *indio_dev = data; /* Handle data ready interrupt from C4/EVENT/DREADY pin */ - if (!iio_device_claim_buffer_mode(indio_dev)) { + if (iio_device_try_claim_buffer_mode(indio_dev)) { ade9000_iio_push_buffer(indio_dev); iio_device_release_buffer_mode(indio_dev); } diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c index 9ac80e4b7d75..ef53066b1735 100644 --- a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c @@ -188,11 +188,8 @@ int cros_ec_sensors_push_data(struct iio_dev *indio_dev, /* * Ignore samples if the buffer is not set: it is needed if the ODR is * set but the buffer is not enabled yet. - * - * Note: iio_device_claim_buffer_mode() returns -EBUSY if the buffer - * is not enabled. */ - if (iio_device_claim_buffer_mode(indio_dev) < 0) + if (!iio_device_try_claim_buffer_mode(indio_dev)) return 0; out = (s16 *)st->samples; diff --git a/drivers/iio/health/max30100.c b/drivers/iio/health/max30100.c index 3d441013893c..7dfdb5eb305e 100644 --- a/drivers/iio/health/max30100.c +++ b/drivers/iio/health/max30100.c @@ -417,13 +417,7 @@ static int max30100_read_raw(struct iio_dev *indio_dev, * Temperature reading can only be acquired while engine * is running */ - if (iio_device_claim_buffer_mode(indio_dev)) { - /* - * Replacing -EBUSY or other error code - * returned by iio_device_claim_buffer_mode() - * because user space may rely on the current - * one. - */ + if (!iio_device_try_claim_buffer_mode(indio_dev)) { ret = -EAGAIN; } else { ret = max30100_get_temp(data, val); diff --git a/drivers/iio/health/max30102.c b/drivers/iio/health/max30102.c index a48c0881a4c7..6918fcb5de2b 100644 --- a/drivers/iio/health/max30102.c +++ b/drivers/iio/health/max30102.c @@ -476,7 +476,7 @@ static int max30102_read_raw(struct iio_dev *indio_dev, * shutdown; leave shutdown briefly when buffer not running */ any_mode_retry: - if (iio_device_claim_buffer_mode(indio_dev)) { + if (!iio_device_try_claim_buffer_mode(indio_dev)) { /* * This one is a *bit* hacky. If we cannot claim buffer * mode, then try direct mode so that we make sure diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 0f8e3aa98b72..3115d59c1372 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -2183,7 +2183,7 @@ EXPORT_SYMBOL_GPL(__devm_iio_device_register); * * There are very few cases where a driver actually needs to lock the current * mode unconditionally. It's recommended to use iio_device_claim_direct() or - * iio_device_claim_buffer_mode() pairs or related helpers instead. + * iio_device_try_claim_buffer_mode() pairs or related helpers instead. */ void __iio_dev_mode_lock(struct iio_dev *indio_dev) { @@ -2201,46 +2201,6 @@ void __iio_dev_mode_unlock(struct iio_dev *indio_dev) } EXPORT_SYMBOL_GPL(__iio_dev_mode_unlock); -/** - * iio_device_claim_buffer_mode - Keep device in buffer mode - * @indio_dev: the iio_dev associated with the device - * - * If the device is in buffer mode it is guaranteed to stay - * that way until iio_device_release_buffer_mode() is called. - * - * Use with iio_device_release_buffer_mode(). - * - * Returns: 0 on success, -EBUSY on failure. - */ -int iio_device_claim_buffer_mode(struct iio_dev *indio_dev) -{ - struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); - - mutex_lock(&iio_dev_opaque->mlock); - - if (iio_buffer_enabled(indio_dev)) - return 0; - - mutex_unlock(&iio_dev_opaque->mlock); - return -EBUSY; -} -EXPORT_SYMBOL_GPL(iio_device_claim_buffer_mode); - -/** - * iio_device_release_buffer_mode - releases claim on buffer mode - * @indio_dev: the iio_dev associated with the device - * - * Release the claim. Device is no longer guaranteed to stay - * in buffer mode. - * - * Use with iio_device_claim_buffer_mode(). - */ -void iio_device_release_buffer_mode(struct iio_dev *indio_dev) -{ - mutex_unlock(&to_iio_dev_opaque(indio_dev)->mlock); -} -EXPORT_SYMBOL_GPL(iio_device_release_buffer_mode); - /** * iio_device_get_current_mode() - helper function providing read-only access to * the opaque @currentmode variable diff --git a/drivers/iio/light/opt4060.c b/drivers/iio/light/opt4060.c index 981c704e7df5..8c4a1f562a83 100644 --- a/drivers/iio/light/opt4060.c +++ b/drivers/iio/light/opt4060.c @@ -304,7 +304,7 @@ static int opt4060_set_driver_state(struct iio_dev *indio_dev, struct opt4060_chip *chip = iio_priv(indio_dev); int ret = 0; any_mode_retry: - if (iio_device_claim_buffer_mode(indio_dev)) { + if (!iio_device_try_claim_buffer_mode(indio_dev)) { /* * This one is a *bit* hacky. If we cannot claim buffer mode, * then try direct mode so that we make sure things cannot diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h index e263ab5eeccf..36bd14e93a75 100644 --- a/include/linux/iio/iio.h +++ b/include/linux/iio/iio.h @@ -706,8 +706,39 @@ static inline bool iio_device_claim_direct(struct iio_dev *indio_dev) */ #define iio_device_release_direct(indio_dev) __iio_dev_mode_unlock(indio_dev) -int iio_device_claim_buffer_mode(struct iio_dev *indio_dev); -void iio_device_release_buffer_mode(struct iio_dev *indio_dev); +/** + * iio_device_try_claim_buffer_mode() - Keep device in buffer mode + * @indio_dev: the iio_dev associated with the device + * + * If the device is in buffer mode it is guaranteed to stay + * that way until iio_device_release_buffer_mode() is called. + * + * Use with iio_device_release_buffer_mode(). + * + * Returns: true on success, false on failure. + */ +static inline bool iio_device_try_claim_buffer_mode(struct iio_dev *indio_dev) +{ + __iio_dev_mode_lock(indio_dev); + + if (!iio_buffer_enabled(indio_dev)) { + __iio_dev_mode_unlock(indio_dev); + return false; + } + + return true; +} + +/** + * iio_device_release_buffer_mode() - releases claim on buffer mode + * @indio_dev: the iio_dev associated with the device + * + * Release the claim. Device is no longer guaranteed to stay + * in buffer mode. + * + * Use with iio_device_try_claim_buffer_mode(). + */ +#define iio_device_release_buffer_mode(indio_dev) __iio_dev_mode_unlock(indio_dev) extern const struct bus_type iio_bus_type; From 7a38b75da1dc38a8cd9563c41b87367b2475b975 Mon Sep 17 00:00:00 2001 From: Kurt Borja Date: Tue, 20 Jan 2026 01:20:44 -0500 Subject: [PATCH 197/297] iio: core: Add cleanup.h support for iio_device_claim_*() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add guard classes for iio_device_claim_*() conditional locks. This will aid drivers write safer and cleaner code when dealing with some common patterns. These classes are not meant to be used directly by drivers (hence the __priv__ prefix). Instead, documented wrapper macros are provided to enforce the use of ACQUIRE() or guard() semantics and avoid the problematic scoped guard. Suggested-by: Andy Shevchenko Reviewed-by: David Lechner Reviewed-by: Nuno Sá Acked-by: Andy Shevchenko Signed-off-by: Kurt Borja Signed-off-by: Jonathan Cameron --- include/linux/iio/iio.h | 65 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h index 36bd14e93a75..a9ecff191bd9 100644 --- a/include/linux/iio/iio.h +++ b/include/linux/iio/iio.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -740,6 +741,70 @@ static inline bool iio_device_try_claim_buffer_mode(struct iio_dev *indio_dev) */ #define iio_device_release_buffer_mode(indio_dev) __iio_dev_mode_unlock(indio_dev) +/* + * These classes are not meant to be used directly by drivers (hence the + * __priv__ prefix). Instead, documented wrapper macros are provided below to + * enforce the use of ACQUIRE() or guard() semantics and avoid the problematic + * scoped guard variants. + */ +DEFINE_GUARD(__priv__iio_dev_mode_lock, struct iio_dev *, + __iio_dev_mode_lock(_T), __iio_dev_mode_unlock(_T)); +DEFINE_GUARD_COND(__priv__iio_dev_mode_lock, _try_direct, + iio_device_claim_direct(_T)); + +/** + * IIO_DEV_ACQUIRE_DIRECT_MODE() - Tries to acquire the direct mode lock with + * automatic release + * @dev: IIO device instance + * @claim: Variable identifier to store acquire result + * + * Tries to acquire the direct mode lock with cleanup ACQUIRE() semantics and + * automatically releases it at the end of the scope. It most be always paired + * with IIO_DEV_ACQUIRE_ERR(), for example (notice the scope braces):: + * + * switch() { + * case IIO_CHAN_INFO_RAW: { + * IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + * if (IIO_DEV_ACQUIRE_FAILED(claim)) + * return -EBUSY; + * + * ... + * } + * case IIO_CHAN_INFO_SCALE: + * ... + * ... + * } + * + * Context: Can sleep + */ +#define IIO_DEV_ACQUIRE_DIRECT_MODE(dev, claim) \ + ACQUIRE(__priv__iio_dev_mode_lock_try_direct, claim)(dev) + +/** + * IIO_DEV_ACQUIRE_FAILED() - ACQUIRE_ERR() wrapper + * @claim: The claim variable passed to IIO_DEV_ACQUIRE_*_MODE() + * + * Return: true if failed to acquire the mode, otherwise false. + */ +#define IIO_DEV_ACQUIRE_FAILED(claim) \ + ACQUIRE_ERR(__priv__iio_dev_mode_lock_try_direct, &(claim)) + +/** + * IIO_DEV_GUARD_CURRENT_MODE() - Acquires the mode lock with automatic release + * @dev: IIO device instance + * + * Acquires the mode lock with cleanup guard() semantics. It is usually paired + * with iio_buffer_enabled(). + * + * This should *not* be used to protect internal driver state and it's use in + * general is *strongly* discouraged. Use any of the IIO_DEV_ACQUIRE_*_MODE() + * variants. + * + * Context: Can sleep + */ +#define IIO_DEV_GUARD_CURRENT_MODE(dev) \ + guard(__priv__iio_dev_mode_lock)(dev) + extern const struct bus_type iio_bus_type; /** From 6a3fe0fc9e623352ccf65650cb31c4309f853d1d Mon Sep 17 00:00:00 2001 From: Kurt Borja Date: Tue, 20 Jan 2026 01:20:45 -0500 Subject: [PATCH 198/297] iio: light: vcnl4000: Use IIO cleanup helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use IIO_DEV_ACQUIRE_DIRECT_MODE() helper to automatically release direct mode. Reviewed-by: David Lechner Reviewed-by: Nuno Sá Signed-off-by: Kurt Borja Signed-off-by: Jonathan Cameron --- drivers/iio/light/vcnl4000.c | 49 +++++++++++++----------------------- 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/drivers/iio/light/vcnl4000.c b/drivers/iio/light/vcnl4000.c index 4dbb2294a843..a36c23813679 100644 --- a/drivers/iio/light/vcnl4000.c +++ b/drivers/iio/light/vcnl4000.c @@ -1078,20 +1078,17 @@ static int vcnl4010_read_raw(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_RAW: - case IIO_CHAN_INFO_SCALE: - if (!iio_device_claim_direct(indio_dev)) + case IIO_CHAN_INFO_SCALE: { + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) return -EBUSY; /* Protect against event capture. */ - if (vcnl4010_is_in_periodic_mode(data)) { - ret = -EBUSY; - } else { - ret = vcnl4000_read_raw(indio_dev, chan, val, val2, - mask); - } + if (vcnl4010_is_in_periodic_mode(data)) + return -EBUSY; - iio_device_release_direct(indio_dev); - return ret; + return vcnl4000_read_raw(indio_dev, chan, val, val2, mask); + } case IIO_CHAN_INFO_SAMP_FREQ: switch (chan->type) { case IIO_PROXIMITY: @@ -1148,36 +1145,27 @@ static int vcnl4010_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask) { - int ret; struct vcnl4000_data *data = iio_priv(indio_dev); - if (!iio_device_claim_direct(indio_dev)) + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) return -EBUSY; /* Protect against event capture. */ - if (vcnl4010_is_in_periodic_mode(data)) { - ret = -EBUSY; - goto end; - } + if (vcnl4010_is_in_periodic_mode(data)) + return -EBUSY; switch (mask) { case IIO_CHAN_INFO_SAMP_FREQ: switch (chan->type) { case IIO_PROXIMITY: - ret = vcnl4010_write_proxy_samp_freq(data, val, val2); - goto end; + return vcnl4010_write_proxy_samp_freq(data, val, val2); default: - ret = -EINVAL; - goto end; + return -EINVAL; } default: - ret = -EINVAL; - goto end; + return -EINVAL; } - -end: - iio_device_release_direct(indio_dev); - return ret; } static int vcnl4010_read_event(struct iio_dev *indio_dev, @@ -1438,14 +1426,13 @@ static int vcnl4010_config_threshold_disable(struct vcnl4000_data *data) static int vcnl4010_config_threshold(struct iio_dev *indio_dev, bool state) { struct vcnl4000_data *data = iio_priv(indio_dev); - int ret; if (state) { - if (!iio_device_claim_direct(indio_dev)) + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) return -EBUSY; - ret = vcnl4010_config_threshold_enable(data); - iio_device_release_direct(indio_dev); - return ret; + + return vcnl4010_config_threshold_enable(data); } else { return vcnl4010_config_threshold_disable(data); } From 421ac0c231cd66bd8cd7fea6a7b79a59ea2f7f1a Mon Sep 17 00:00:00 2001 From: Kurt Borja Date: Tue, 20 Jan 2026 01:20:46 -0500 Subject: [PATCH 199/297] iio: health: max30102: Use IIO cleanup helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use IIO_DEV_GUARD_CURRENT_MODE() cleanup helper to simplify and drop busy-waiting code in max30102_read_raw(). Reviewed-by: David Lechner Reviewed-by: Nuno Sá Signed-off-by: Kurt Borja Signed-off-by: Jonathan Cameron --- drivers/iio/health/max30102.c | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/drivers/iio/health/max30102.c b/drivers/iio/health/max30102.c index 6918fcb5de2b..47da44efd68b 100644 --- a/drivers/iio/health/max30102.c +++ b/drivers/iio/health/max30102.c @@ -467,44 +467,29 @@ static int max30102_read_raw(struct iio_dev *indio_dev, int *val, int *val2, long mask) { struct max30102_data *data = iio_priv(indio_dev); - int ret = -EINVAL; + int ret; switch (mask) { - case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_RAW: { /* * Temperature reading can only be acquired when not in * shutdown; leave shutdown briefly when buffer not running */ -any_mode_retry: - if (!iio_device_try_claim_buffer_mode(indio_dev)) { - /* - * This one is a *bit* hacky. If we cannot claim buffer - * mode, then try direct mode so that we make sure - * things cannot concurrently change. And we just keep - * trying until we get one of the modes... - */ - if (!iio_device_claim_direct(indio_dev)) - goto any_mode_retry; + IIO_DEV_GUARD_CURRENT_MODE(indio_dev); - ret = max30102_get_temp(data, val, true); - iio_device_release_direct(indio_dev); - } else { - ret = max30102_get_temp(data, val, false); - iio_device_release_buffer_mode(indio_dev); - } + ret = max30102_get_temp(data, val, !iio_buffer_enabled(indio_dev)); if (ret) return ret; - ret = IIO_VAL_INT; - break; + return IIO_VAL_INT; + } case IIO_CHAN_INFO_SCALE: *val = 1000; /* 62.5 */ *val2 = 16; - ret = IIO_VAL_FRACTIONAL; - break; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; } - - return ret; } static const struct iio_info max30102_info = { From 8284a498541623414abc616450371b08f24a4aac Mon Sep 17 00:00:00 2001 From: Kurt Borja Date: Tue, 20 Jan 2026 01:20:47 -0500 Subject: [PATCH 200/297] iio: light: opt4060: Use IIO cleanup helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use IIO_DEV_GUARD_CURRENT_MODE() cleanup helper to simplify and drop busy-waiting code in opt4060_set_driver_state(). Reviewed-by: David Lechner Reviewed-by: Nuno Sá Signed-off-by: Kurt Borja Signed-off-by: Jonathan Cameron --- drivers/iio/light/opt4060.c | 52 ++++++++++++------------------------- 1 file changed, 17 insertions(+), 35 deletions(-) diff --git a/drivers/iio/light/opt4060.c b/drivers/iio/light/opt4060.c index 8c4a1f562a83..d6e915ab355d 100644 --- a/drivers/iio/light/opt4060.c +++ b/drivers/iio/light/opt4060.c @@ -302,41 +302,23 @@ static int opt4060_set_driver_state(struct iio_dev *indio_dev, bool continuous_irq) { struct opt4060_chip *chip = iio_priv(indio_dev); - int ret = 0; -any_mode_retry: - if (!iio_device_try_claim_buffer_mode(indio_dev)) { - /* - * This one is a *bit* hacky. If we cannot claim buffer mode, - * then try direct mode so that we make sure things cannot - * concurrently change. And we just keep trying until we get one - * of the modes... - */ - if (!iio_device_claim_direct(indio_dev)) - goto any_mode_retry; - /* - * This path means that we managed to claim direct mode. In - * this case the buffer isn't enabled and it's okay to leave - * continuous mode for sampling and/or irq. - */ - ret = opt4060_set_state_common(chip, continuous_sampling, - continuous_irq); - iio_device_release_direct(indio_dev); - return ret; - } else { - /* - * This path means that we managed to claim buffer mode. In - * this case the buffer is enabled and irq and sampling must go - * to or remain continuous, but only if the trigger is from this - * device. - */ - if (!iio_trigger_validate_own_device(indio_dev->trig, indio_dev)) - ret = opt4060_set_state_common(chip, true, true); - else - ret = opt4060_set_state_common(chip, continuous_sampling, - continuous_irq); - iio_device_release_buffer_mode(indio_dev); - } - return ret; + + IIO_DEV_GUARD_CURRENT_MODE(indio_dev); + + /* + * If we manage to claim buffer mode and we are using our own trigger, + * IRQ and sampling must go to or remain continuous. + */ + if (iio_buffer_enabled(indio_dev) && + iio_trigger_validate_own_device(indio_dev->trig, indio_dev)) + return opt4060_set_state_common(chip, true, true); + + /* + * This path means that we managed to claim direct mode. In this case + * the buffer isn't enabled and it's okay to leave continuous mode for + * sampling and/or irq. + */ + return opt4060_set_state_common(chip, continuous_sampling, continuous_irq); } /* From 2f55ae3a891e3fea38327a7abb2c1f3679e8543a Mon Sep 17 00:00:00 2001 From: Jonathan Santos Date: Wed, 14 Jan 2026 06:26:39 -0300 Subject: [PATCH 201/297] dt-bindings: iio: adc: ad7768-1: add new supported parts Add compatibles for supported parts in the ad7768-1 family: ADAQ7767-1, ADAQ7768-1 and ADAQ7769-1 Add property and checks for AFF gain, supported by ADAQ7767-1 and ADAQ7769-1, and for PGA gain, supported by ADAQ7768-1 and ADAQ7769-1: adi,aaf-gain-bp pga-gpios Reviewed-by: Krzysztof Kozlowski Signed-off-by: Jonathan Santos Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/adi,ad7768-1.yaml | 64 +++++++++++++++++-- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7768-1.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7768-1.yaml index c06d0fc791d3..dfa2d7fa9fb3 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad7768-1.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7768-1.yaml @@ -4,18 +4,26 @@ $id: http://devicetree.org/schemas/iio/adc/adi,ad7768-1.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Analog Devices AD7768-1 ADC device driver +title: Analog Devices AD7768-1 ADC family maintainers: - Michael Hennerich description: | - Datasheet at: - https://www.analog.com/media/en/technical-documentation/data-sheets/ad7768-1.pdf + Analog Devices AD7768-1 24-Bit Single Channel Low Power sigma-delta ADC family + + https://www.analog.com/media/en/technical-documentation/data-sheets/ad7768-1.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/adaq7767-1.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/adaq7768-1.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/adaq7769-1.pdf properties: compatible: - const: adi,ad7768-1 + enum: + - adi,ad7768-1 + - adi,adaq7767-1 + - adi,adaq7768-1 + - adi,adaq7769-1 reg: maxItems: 1 @@ -58,6 +66,25 @@ properties: description: ADC reference voltage supply + adi,aaf-gain-bp: + description: | + Specifies the gain applied by the Analog Anti-Aliasing Filter (AAF) + to the ADC input in basis points (one hundredth of a percent). + The hardware gain is determined by which input pin(s) the signal goes + through into the AAF. The possible connections are: + * For the ADAQ7767-1: Input connected to IN1±, IN2± or IN3±. + * For the ADAQ7769-1: OUT_PGA pin connected to IN1_AAF+, IN2_AAF+, + or IN3_AAF+. + enum: [1430, 3640, 10000] + default: 10000 + + pga-gpios: + description: + GAIN 0, GAIN1 and GAIN2 pins for gain selection. For devices that have + PGA configuration input pins, pga-gpios must be defined. + minItems: 3 + maxItems: 3 + adi,sync-in-gpios: maxItems: 1 description: @@ -147,6 +174,35 @@ patternProperties: allOf: - $ref: /schemas/spi/spi-peripheral-props.yaml# + # AAF Gain property only applies to ADAQ7767-1 and ADAQ7769-1 devices + - if: + properties: + compatible: + contains: + enum: + - adi,adaq7767-1 + - adi,adaq7769-1 + then: + required: + - adi,aaf-gain-bp + else: + properties: + adi,aaf-gain-bp: false + + - if: + properties: + compatible: + contains: + enum: + - adi,adaq7768-1 + - adi,adaq7769-1 + then: + required: + - pga-gpios + else: + properties: + pga-gpios: false + unevaluatedProperties: false examples: From fa087f5babbc24db76d5b24073216fa2d7d7b57d Mon Sep 17 00:00:00 2001 From: Jonathan Santos Date: Wed, 14 Jan 2026 06:26:52 -0300 Subject: [PATCH 202/297] iio: adc: ad7768-1: introduce chip info for future multidevice support Add Chip info struct in SPI device to store channel information for each supported part. Signed-off-by: Jonathan Santos Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7768-1.c | 64 ++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/drivers/iio/adc/ad7768-1.c b/drivers/iio/adc/ad7768-1.c index d96802b7847a..89b0ca8f584c 100644 --- a/drivers/iio/adc/ad7768-1.c +++ b/drivers/iio/adc/ad7768-1.c @@ -213,6 +213,12 @@ static const struct iio_scan_type ad7768_scan_type[] = { }, }; +struct ad7768_chip_info { + const char *name; + const struct iio_chan_spec *channel_spec; + int num_channels; +}; + struct ad7768_state { struct spi_device *spi; struct regmap *regmap; @@ -234,6 +240,7 @@ struct ad7768_state { struct gpio_desc *gpio_reset; const char *labels[AD7768_MAX_CHANNELS]; struct gpio_chip gpiochip; + const struct ad7768_chip_info *chip; bool en_spi_sync; /* * DMA (thus cache coherency maintenance) may require the @@ -748,24 +755,28 @@ static const struct iio_chan_spec_ext_info ad7768_ext_info[] = { { } }; +#define AD7768_CHAN(_idx, _msk_avail) \ +{ \ + .type = IIO_VOLTAGE, \ + .info_mask_separate_available = _msk_avail, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .ext_info = ad7768_ext_info, \ + .indexed = 1, \ + .channel = _idx, \ + .scan_index = _idx, \ + .has_ext_scan_type = 1, \ + .ext_scan_type = ad7768_scan_type, \ + .num_ext_scan_type = ARRAY_SIZE(ad7768_scan_type), \ +} + static const struct iio_chan_spec ad7768_channels[] = { - { - .type = IIO_VOLTAGE, - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), - .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | - BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | - BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), - .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), - .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), - .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), - .ext_info = ad7768_ext_info, - .indexed = 1, - .channel = 0, - .scan_index = 0, - .has_ext_scan_type = 1, - .ext_scan_type = ad7768_scan_type, - .num_ext_scan_type = ARRAY_SIZE(ad7768_scan_type), - }, + AD7768_CHAN(0, 0), }; static int ad7768_read_raw(struct iio_dev *indio_dev, @@ -1321,6 +1332,12 @@ static int ad7768_register_regulators(struct device *dev, struct ad7768_state *s return 0; } +static const struct ad7768_chip_info ad7768_chip_info = { + .name = "ad7768-1", + .channel_spec = ad7768_channels, + .num_channels = ARRAY_SIZE(ad7768_channels), +}; + static int ad7768_probe(struct spi_device *spi) { struct ad7768_state *st; @@ -1347,6 +1364,7 @@ static int ad7768_probe(struct spi_device *spi) return ret; } + st->chip = spi_get_device_match_data(spi); st->spi = spi; st->regmap = devm_regmap_init_spi(spi, &ad7768_regmap_config); @@ -1371,9 +1389,9 @@ static int ad7768_probe(struct spi_device *spi) st->mclk_freq = clk_get_rate(st->mclk); - indio_dev->channels = ad7768_channels; - indio_dev->num_channels = ARRAY_SIZE(ad7768_channels); - indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->channels = st->chip->channel_spec; + indio_dev->num_channels = st->chip->num_channels; + indio_dev->name = st->chip->name; indio_dev->info = &ad7768_info; indio_dev->modes = INDIO_DIRECT_MODE; @@ -1390,7 +1408,7 @@ static int ad7768_probe(struct spi_device *spi) init_completion(&st->completion); - ret = ad7768_set_channel_label(indio_dev, ARRAY_SIZE(ad7768_channels)); + ret = ad7768_set_channel_label(indio_dev, st->chip->num_channels); if (ret) return ret; @@ -1409,13 +1427,13 @@ static int ad7768_probe(struct spi_device *spi) } static const struct spi_device_id ad7768_id_table[] = { - { "ad7768-1", 0 }, + { "ad7768-1", (kernel_ulong_t)&ad7768_chip_info }, { } }; MODULE_DEVICE_TABLE(spi, ad7768_id_table); static const struct of_device_id ad7768_of_match[] = { - { .compatible = "adi,ad7768-1" }, + { .compatible = "adi,ad7768-1", .data = &ad7768_chip_info }, { } }; MODULE_DEVICE_TABLE(of, ad7768_of_match); From 404a3b4c36f2e1987ff28d1d61d309292d2e33cb Mon Sep 17 00:00:00 2001 From: Jonathan Santos Date: Wed, 14 Jan 2026 06:27:03 -0300 Subject: [PATCH 203/297] units: add conversion macros for percentage related units Add macros to convert between ratio and percentage related units, including percent (1/100), permille (1/1,000), permyriad (1/10,000, also equivalent to one Basis point) and per cent mille (1/100,000). Those are Used for precise fractional calculations in engineering, finance, and measurement applications. Signed-off-by: Jonathan Santos Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- include/linux/units.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/include/linux/units.h b/include/linux/units.h index 00e15de33eca..3471c5a38dcf 100644 --- a/include/linux/units.h +++ b/include/linux/units.h @@ -21,6 +21,25 @@ #define PICO 1000000000000ULL #define FEMTO 1000000000000000ULL +/* + * Percentage and related scaling units + * + * These macros define scaling factors used to convert between ratio and + * percentage-based representations with different decimal resolutions. + * They are used for precise fractional calculations in engineering, finance, + * and measurement applications. + * + * Examples: + * 1% = 0.01 = 1 / PERCENT + * 0.1% = 0.001 = 1 / PERMILLE + * 0.01% = 0.0001 = 1 / PERMYRIAD (1 basis point) + * 0.001% = 0.00001 = 1 / PERCENTMILLE + */ +#define PERCENT 100 +#define PERMILLE 1000 +#define PERMYRIAD 10000 +#define PERCENTMILLE 100000 + #define NANOHZ_PER_HZ 1000000000UL #define MICROHZ_PER_HZ 1000000UL #define MILLIHZ_PER_HZ 1000UL From e7b0312c60a67431ad249b95c624b98fa782f33f Mon Sep 17 00:00:00 2001 From: Jonathan Santos Date: Wed, 14 Jan 2026 06:27:14 -0300 Subject: [PATCH 204/297] iio: adc: ad7768-1: refactor ad7768_write_raw() Squash __ad7768_write_raw() back to ad7768_write_raw() to allow the addition of new attributes without requiring a direct mode claim. Signed-off-by: Jonathan Santos Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7768-1.c | 50 ++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/drivers/iio/adc/ad7768-1.c b/drivers/iio/adc/ad7768-1.c index 89b0ca8f584c..bd4b2e090c5b 100644 --- a/drivers/iio/adc/ad7768-1.c +++ b/drivers/iio/adc/ad7768-1.c @@ -742,6 +742,19 @@ static int ad7768_get_filter_type_attr(struct iio_dev *dev, return ad7768_filter_regval_to_type[FIELD_GET(mask, mode)]; } +static int ad7768_update_dec_rate(struct iio_dev *dev, unsigned int dec_rate) +{ + struct ad7768_state *st = iio_priv(dev); + int ret; + + ret = ad7768_configure_dig_fil(dev, st->filter_type, dec_rate); + if (ret) + return ret; + + /* Update sampling frequency */ + return ad7768_set_freq(st, st->samp_freq); +} + static const struct iio_enum ad7768_filter_type_iio_enum = { .items = ad7768_filter_enum, .num_items = ARRAY_SIZE(ad7768_filter_enum), @@ -867,44 +880,33 @@ static int ad7768_read_avail(struct iio_dev *indio_dev, } } -static int __ad7768_write_raw(struct iio_dev *indio_dev, - struct iio_chan_spec const *chan, - int val, int val2, long info) +static int ad7768_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) { struct ad7768_state *st = iio_priv(indio_dev); int ret; switch (info) { case IIO_CHAN_INFO_SAMP_FREQ: - return ad7768_set_freq(st, val); + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ad7768_set_freq(st, val); + iio_device_release_direct(indio_dev); + return ret; case IIO_CHAN_INFO_OVERSAMPLING_RATIO: - ret = ad7768_configure_dig_fil(indio_dev, st->filter_type, val); - if (ret) - return ret; + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; - /* Update sampling frequency */ - return ad7768_set_freq(st, st->samp_freq); + ret = ad7768_update_dec_rate(indio_dev, val); + iio_device_release_direct(indio_dev); + return ret; default: return -EINVAL; } } -static int ad7768_write_raw(struct iio_dev *indio_dev, - struct iio_chan_spec const *chan, - int val, int val2, long info) -{ - int ret; - - if (!iio_device_claim_direct(indio_dev)) - return -EBUSY; - - ret = __ad7768_write_raw(indio_dev, chan, val, val2, info); - iio_device_release_direct(indio_dev); - - return ret; -} - static int ad7768_read_label(struct iio_dev *indio_dev, const struct iio_chan_spec *chan, char *label) { From ff085189cb1703b3be8176310545afbe544cd1f4 Mon Sep 17 00:00:00 2001 From: Jonathan Santos Date: Wed, 14 Jan 2026 06:27:29 -0300 Subject: [PATCH 205/297] iio: adc: ad7768-1: add support for ADAQ776x-1 ADC Family Add support for ADAQ7767/68/69-1 series, which includes PGIA and Anti-aliasing filter (AAF) gains. Unlike the AD7768-1, they do not provide a VCM regulator interface. The PGA gain is configured in run-time through the scale attribute, if supported by the device. PGA is controlled by GPIOs provided in the device tree. The AAF gain is defined by hardware connections and should be specified in the device tree. Signed-off-by: Jonathan Santos Signed-off-by: Jonathan Cameron --- drivers/iio/adc/Kconfig | 1 + drivers/iio/adc/ad7768-1.c | 311 ++++++++++++++++++++++++++++++++++++- 2 files changed, 308 insertions(+), 4 deletions(-) diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index b4295aa415bf..60038ae8dfc4 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -411,6 +411,7 @@ config AD7768_1 depends on SPI select REGULATOR select REGMAP_SPI + select RATIONAL select IIO_BUFFER select IIO_TRIGGER select IIO_TRIGGERED_BUFFER diff --git a/drivers/iio/adc/ad7768-1.c b/drivers/iio/adc/ad7768-1.c index bd4b2e090c5b..980c079ab41a 100644 --- a/drivers/iio/adc/ad7768-1.c +++ b/drivers/iio/adc/ad7768-1.c @@ -6,6 +6,7 @@ */ #include #include +#include #include #include #include @@ -14,8 +15,12 @@ #include #include #include +#include +#include #include #include +#include +#include #include #include #include @@ -107,10 +112,15 @@ #define AD7768_VCM_OFF 0x07 +#define ADAQ776X_GAIN_MAX_NANO (128 * NANO) +#define ADAQ776X_MAX_GAIN_MODES 8 + #define AD7768_TRIGGER_SOURCE_SYNC_IDX 0 #define AD7768_MAX_CHANNELS 1 +#define ADAQ7768_PGA_PINS 3 + enum ad7768_conv_mode { AD7768_CONTINUOUS, AD7768_ONE_SHOT, @@ -153,6 +163,51 @@ enum ad7768_scan_type { AD7768_SCAN_TYPE_HIGH_SPEED, }; +enum { + AD7768_PGA_GAIN_0, + AD7768_PGA_GAIN_1, + AD7768_PGA_GAIN_2, + AD7768_PGA_GAIN_3, + AD7768_PGA_GAIN_4, + AD7768_PGA_GAIN_5, + AD7768_PGA_GAIN_6, + AD7768_PGA_GAIN_7, +}; + +enum { + AD7768_AAF_IN1, + AD7768_AAF_IN2, + AD7768_AAF_IN3, +}; + +/* PGA and AAF gains in V/V */ +static const int adaq7768_gains[] = { + [AD7768_PGA_GAIN_0] = 325, /* 0.325 */ + [AD7768_PGA_GAIN_1] = 650, /* 0.650 */ + [AD7768_PGA_GAIN_2] = 1300, /* 1.300 */ + [AD7768_PGA_GAIN_3] = 2600, /* 2.600 */ + [AD7768_PGA_GAIN_4] = 5200, /* 5.200 */ + [AD7768_PGA_GAIN_5] = 10400, /* 10.400 */ + [AD7768_PGA_GAIN_6] = 20800, /* 20.800 */ +}; + +static const int adaq7769_gains[] = { + [AD7768_PGA_GAIN_0] = 1000, /* 1.000 */ + [AD7768_PGA_GAIN_1] = 2000, /* 2.000 */ + [AD7768_PGA_GAIN_2] = 4000, /* 4.000 */ + [AD7768_PGA_GAIN_3] = 8000, /* 8.000 */ + [AD7768_PGA_GAIN_4] = 16000, /* 16.000 */ + [AD7768_PGA_GAIN_5] = 32000, /* 32.000 */ + [AD7768_PGA_GAIN_6] = 64000, /* 64.000 */ + [AD7768_PGA_GAIN_7] = 128000, /* 128.000 */ +}; + +static const int ad7768_aaf_gains_bp[] = { + [AD7768_AAF_IN1] = 10000, /* 1.000 */ + [AD7768_AAF_IN2] = 3640, /* 0.364 */ + [AD7768_AAF_IN3] = 1430, /* 0.143 */ +}; + /* -3dB cutoff frequency multipliers (relative to ODR) for each filter type. */ static const int ad7768_filter_3db_odr_multiplier[] = { [AD7768_FILTER_SINC5] = 204, /* 0.204 */ @@ -217,6 +272,13 @@ struct ad7768_chip_info { const char *name; const struct iio_chan_spec *channel_spec; int num_channels; + const int *pga_gains; + int num_pga_modes; + int default_pga_mode; + int pgia_mode2pin_offset; + bool has_pga; + bool has_variable_aaf; + bool has_vcm_regulator; }; struct ad7768_state { @@ -234,14 +296,19 @@ struct ad7768_state { unsigned int samp_freq; unsigned int samp_freq_avail[ARRAY_SIZE(ad7768_mclk_div_rates)]; unsigned int samp_freq_avail_len; + unsigned int pga_gain_mode; + unsigned int aaf_gain; + int scale_tbl[ADAQ776X_MAX_GAIN_MODES][2]; struct completion completion; struct iio_trigger *trig; + struct gpio_descs *pga_gpios; struct gpio_desc *gpio_sync_in; struct gpio_desc *gpio_reset; const char *labels[AD7768_MAX_CHANNELS]; struct gpio_chip gpiochip; const struct ad7768_chip_info *chip; bool en_spi_sync; + struct mutex pga_lock; /* protect device internal state (PGA) */ /* * DMA (thus cache coherency maintenance) may require the * transfer buffers to live in their own cache lines. @@ -464,6 +531,42 @@ static int ad7768_reg_access(struct iio_dev *indio_dev, return ret; } +static void ad7768_fill_scale_tbl(struct iio_dev *dev) +{ + struct ad7768_state *st = iio_priv(dev); + const struct iio_scan_type *scan_type; + int val, val2, tmp0, tmp1, i; + struct u32_fract fract; + unsigned long n, d; + u64 tmp2; + + scan_type = iio_get_current_scan_type(dev, &dev->channels[0]); + if (scan_type->sign == 's') + val2 = scan_type->realbits - 1; + else + val2 = scan_type->realbits; + + for (i = 0; i < st->chip->num_pga_modes; i++) { + /* Convert gain to a fraction format */ + fract.numerator = st->chip->pga_gains[i]; + fract.denominator = MILLI; + if (st->chip->has_variable_aaf) { + fract.numerator *= ad7768_aaf_gains_bp[st->aaf_gain]; + fract.denominator *= PERMYRIAD; + } + + rational_best_approximation(fract.numerator, fract.denominator, + INT_MAX, INT_MAX, &n, &d); + + val = mult_frac(st->vref_uv, d, n); + /* Would multiply by NANO here, but value is already in milli */ + tmp2 = ((u64)val * MICRO) >> val2; + tmp0 = div_u64_rem(tmp2, NANO, &tmp1); + st->scale_tbl[i][0] = tmp0; /* Integer part */ + st->scale_tbl[i][1] = abs(tmp1); /* Fractional part */ + } +} + static int ad7768_set_sinc3_dec_rate(struct ad7768_state *st, unsigned int dec_rate) { @@ -565,12 +668,66 @@ static int ad7768_configure_dig_fil(struct iio_dev *dev, st->oversampling_ratio = ad7768_dec_rate_values[dec_rate_idx]; } + /* Update scale table: scale values vary according to the precision */ + ad7768_fill_scale_tbl(dev); + ad7768_fill_samp_freq_tbl(st); /* A sync-in pulse is required after every configuration change */ return ad7768_send_sync_pulse(st); } +static int ad7768_setup_pga(struct device *dev, struct ad7768_state *st) +{ + st->pga_gpios = devm_gpiod_get_array(dev, "pga", GPIOD_OUT_LOW); + if (IS_ERR(st->pga_gpios)) + return dev_err_probe(dev, PTR_ERR(st->pga_gpios), + "Failed to get PGA gpios.\n"); + + if (st->pga_gpios->ndescs != ADAQ7768_PGA_PINS) + return dev_err_probe(dev, -EINVAL, + "Expected %d GPIOs for PGA control.\n", + ADAQ7768_PGA_PINS); + return 0; +} + +static int ad7768_calc_pga_gain(struct ad7768_state *st, int gain_int, + int gain_fract, int precision) +{ + u64 gain_nano; + u32 tmp; + + gain_nano = gain_int * NANO + gain_fract; + gain_nano = clamp(gain_nano, 0, ADAQ776X_GAIN_MAX_NANO); + tmp = DIV_ROUND_CLOSEST_ULL(gain_nano << precision, NANO); + gain_nano = DIV_ROUND_CLOSEST(st->vref_uv, tmp); + if (st->chip->has_variable_aaf) + gain_nano = DIV_ROUND_CLOSEST_ULL(gain_nano * PERMYRIAD, + ad7768_aaf_gains_bp[st->aaf_gain]); + + return find_closest(gain_nano, st->chip->pga_gains, + (int)st->chip->num_pga_modes); +} + +static int ad7768_set_pga_gain(struct ad7768_state *st, + int gain_mode) +{ + int pgia_pins_value = abs(gain_mode - st->chip->pgia_mode2pin_offset); + DECLARE_BITMAP(bitmap, ADAQ7768_PGA_PINS) = { }; + int ret; + + guard(mutex)(&st->pga_lock); + + bitmap_write(bitmap, pgia_pins_value, 0, ADAQ7768_PGA_PINS); + ret = gpiod_multi_set_value_cansleep(st->pga_gpios, bitmap); + if (ret) + return ret; + + st->pga_gain_mode = gain_mode; + + return 0; +} + static int ad7768_gpio_direction_input(struct gpio_chip *chip, unsigned int offset) { struct iio_dev *indio_dev = gpiochip_get_data(chip); @@ -792,6 +949,10 @@ static const struct iio_chan_spec ad7768_channels[] = { AD7768_CHAN(0, 0), }; +static const struct iio_chan_spec adaq776x_channels[] = { + AD7768_CHAN(0, BIT(IIO_CHAN_INFO_SCALE)), +}; + static int ad7768_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long info) @@ -819,7 +980,19 @@ static int ad7768_read_raw(struct iio_dev *indio_dev, return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: - *val = (st->vref_uv * 2) / 1000; + if (st->chip->has_pga) { + guard(mutex)(&st->pga_lock); + + *val = st->scale_tbl[st->pga_gain_mode][0]; + *val2 = st->scale_tbl[st->pga_gain_mode][1]; + return IIO_VAL_INT_PLUS_NANO; + } + + temp = (st->vref_uv * 2) / 1000; + if (st->chip->has_variable_aaf) + temp = (temp * PERMYRIAD) / ad7768_aaf_gains_bp[st->aaf_gain]; + + *val = temp; *val2 = scan_type->realbits; return IIO_VAL_FRACTIONAL_LOG2; @@ -875,18 +1048,39 @@ static int ad7768_read_avail(struct iio_dev *indio_dev, *length = st->samp_freq_avail_len; *type = IIO_VAL_INT; return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + *vals = (int *)st->scale_tbl; + *length = st->chip->num_pga_modes * 2; + *type = IIO_VAL_INT_PLUS_NANO; + return IIO_AVAIL_LIST; default: return -EINVAL; } } +static int ad7768_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + static int ad7768_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long info) { struct ad7768_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; int ret; + scan_type = iio_get_current_scan_type(indio_dev, chan); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + switch (info) { case IIO_CHAN_INFO_SAMP_FREQ: if (!iio_device_claim_direct(indio_dev)) @@ -902,6 +1096,21 @@ static int ad7768_write_raw(struct iio_dev *indio_dev, ret = ad7768_update_dec_rate(indio_dev, val); iio_device_release_direct(indio_dev); return ret; + case IIO_CHAN_INFO_SCALE: { + int gain_mode; + + if (!st->chip->has_pga) + return -EOPNOTSUPP; + + if (scan_type->sign == 's') + gain_mode = ad7768_calc_pga_gain(st, val, val2, + scan_type->realbits - 1); + else + gain_mode = ad7768_calc_pga_gain(st, val, val2, + scan_type->realbits); + + return ad7768_set_pga_gain(st, gain_mode); + } default: return -EINVAL; } @@ -928,6 +1137,7 @@ static const struct iio_info ad7768_info = { .read_raw = &ad7768_read_raw, .read_avail = &ad7768_read_avail, .write_raw = &ad7768_write_raw, + .write_raw_get_fmt = &ad7768_write_raw_get_fmt, .read_label = ad7768_read_label, .get_current_scan_type = &ad7768_get_current_scan_type, .debugfs_reg_access = &ad7768_reg_access, @@ -1311,8 +1521,9 @@ static const struct regulator_desc vcm_desc = { .owner = THIS_MODULE, }; -static int ad7768_register_regulators(struct device *dev, struct ad7768_state *st, - struct iio_dev *indio_dev) +static int ad7768_register_vcm_regulator(struct device *dev, + struct ad7768_state *st, + struct iio_dev *indio_dev) { struct regulator_config config = { .dev = dev, @@ -1334,10 +1545,77 @@ static int ad7768_register_regulators(struct device *dev, struct ad7768_state *s return 0; } +static int ad7768_parse_aaf_gain(struct device *dev, struct ad7768_state *st) +{ + u32 val; + int ret; + + ret = device_property_read_u32(dev, "adi,aaf-gain-bp", &val); + if (ret == -EINVAL) { + /* If controllable, use default */ + if (st->chip->has_variable_aaf) + st->aaf_gain = AD7768_AAF_IN1; + return 0; + } + if (ret) + return dev_err_probe(dev, ret, "Failed to get AAF gain value\n"); + + if (!st->chip->has_variable_aaf) + return dev_err_probe(dev, -EOPNOTSUPP, + "AAF gain provided, but not supported for %s\n", st->chip->name); + + switch (val) { + case 10000: + st->aaf_gain = AD7768_AAF_IN1; + break; + case 3640: + st->aaf_gain = AD7768_AAF_IN2; + break; + case 1430: + st->aaf_gain = AD7768_AAF_IN3; + break; + default: + return dev_err_probe(dev, -EINVAL, "Invalid firmware provided AAF gain\n"); + } + + return 0; +} + static const struct ad7768_chip_info ad7768_chip_info = { .name = "ad7768-1", .channel_spec = ad7768_channels, .num_channels = ARRAY_SIZE(ad7768_channels), + .has_vcm_regulator = true, +}; + +static const struct ad7768_chip_info adaq7767_chip_info = { + .name = "adaq7767-1", + .channel_spec = ad7768_channels, + .num_channels = ARRAY_SIZE(ad7768_channels), + .has_variable_aaf = true, +}; + +static const struct ad7768_chip_info adaq7768_chip_info = { + .name = "adaq7768-1", + .channel_spec = adaq776x_channels, + .num_channels = ARRAY_SIZE(adaq776x_channels), + .pga_gains = adaq7768_gains, + .default_pga_mode = AD7768_PGA_GAIN_2, + .num_pga_modes = ARRAY_SIZE(adaq7768_gains), + .pgia_mode2pin_offset = 6, + .has_pga = true, +}; + +static const struct ad7768_chip_info adaq7769_chip_info = { + .name = "adaq7769-1", + .channel_spec = adaq776x_channels, + .num_channels = ARRAY_SIZE(adaq776x_channels), + .pga_gains = adaq7769_gains, + .default_pga_mode = AD7768_PGA_GAIN_0, + .num_pga_modes = ARRAY_SIZE(adaq7769_gains), + .pgia_mode2pin_offset = 0, + .has_pga = true, + .has_variable_aaf = true, }; static int ad7768_probe(struct spi_device *spi) @@ -1398,7 +1676,13 @@ static int ad7768_probe(struct spi_device *spi) indio_dev->modes = INDIO_DIRECT_MODE; /* Register VCM output regulator */ - ret = ad7768_register_regulators(&spi->dev, st, indio_dev); + if (st->chip->has_vcm_regulator) { + ret = ad7768_register_vcm_regulator(&spi->dev, st, indio_dev); + if (ret) + return ret; + } + + ret = ad7768_parse_aaf_gain(&spi->dev, st); if (ret) return ret; @@ -1409,6 +1693,19 @@ static int ad7768_probe(struct spi_device *spi) } init_completion(&st->completion); + ret = devm_mutex_init(&spi->dev, &st->pga_lock); + if (ret) + return ret; + + if (st->chip->has_pga) { + ret = ad7768_setup_pga(&spi->dev, st); + if (ret) + return ret; + + ret = ad7768_set_pga_gain(st, st->chip->default_pga_mode); + if (ret) + return ret; + } ret = ad7768_set_channel_label(indio_dev, st->chip->num_channels); if (ret) @@ -1430,12 +1727,18 @@ static int ad7768_probe(struct spi_device *spi) static const struct spi_device_id ad7768_id_table[] = { { "ad7768-1", (kernel_ulong_t)&ad7768_chip_info }, + { "adaq7767-1", (kernel_ulong_t)&adaq7767_chip_info }, + { "adaq7768-1", (kernel_ulong_t)&adaq7768_chip_info }, + { "adaq7769-1", (kernel_ulong_t)&adaq7769_chip_info }, { } }; MODULE_DEVICE_TABLE(spi, ad7768_id_table); static const struct of_device_id ad7768_of_match[] = { { .compatible = "adi,ad7768-1", .data = &ad7768_chip_info }, + { .compatible = "adi,adaq7767-1", .data = &adaq7767_chip_info }, + { .compatible = "adi,adaq7768-1", .data = &adaq7768_chip_info }, + { .compatible = "adi,adaq7769-1", .data = &adaq7769_chip_info }, { } }; MODULE_DEVICE_TABLE(of, ad7768_of_match); From b0913a44a9ae02f498682e6010b57f773e44f845 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 18 Jan 2026 13:19:39 +0200 Subject: [PATCH 206/297] iio: pressure: mprls0025pa: remove error message Do not print a duplicate error message if devm_request_irq() fails. Signed-off-by: Petre Rodan Suggested-by: Andy Shevchenko Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/mprls0025pa.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/iio/pressure/mprls0025pa.c b/drivers/iio/pressure/mprls0025pa.c index 587d0dcad89b..e8c495f336ff 100644 --- a/drivers/iio/pressure/mprls0025pa.c +++ b/drivers/iio/pressure/mprls0025pa.c @@ -413,8 +413,7 @@ int mpr_common_probe(struct device *dev, const struct mpr_ops *ops, int irq) ret = devm_request_irq(dev, data->irq, mpr_eoc_handler, 0, dev_name(dev), data); if (ret) - return dev_err_probe(dev, ret, - "request irq %d failed\n", data->irq); + return ret; } data->gpiod_reset = devm_gpiod_get_optional(dev, "reset", From c1b14015224cfcccd5356333763f2f4f401bd810 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Mon, 19 Jan 2026 11:23:16 +0100 Subject: [PATCH 207/297] iio: accel: adxl380: Avoid reading more entries than present in FIFO The interrupt handler reads FIFO entries in batches of N samples, where N is the number of scan elements that have been enabled. However, the sensor fills the FIFO one sample at a time, even when more than one channel is enabled. Therefore,the number of entries reported by the FIFO status registers may not be a multiple of N; if this number is not a multiple, the number of entries read from the FIFO may exceed the number of entries actually present. To fix the above issue, round down the number of FIFO entries read from the status registers so that it is always a multiple of N. Fixes: df36de13677a ("iio: accel: add ADXL380 driver") Signed-off-by: Francesco Lavra Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl380.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/iio/accel/adxl380.c b/drivers/iio/accel/adxl380.c index ba550142866a..a77c2323d1aa 100644 --- a/drivers/iio/accel/adxl380.c +++ b/drivers/iio/accel/adxl380.c @@ -966,6 +966,7 @@ static irqreturn_t adxl380_irq_handler(int irq, void *p) if (ret) return IRQ_HANDLED; + fifo_entries = rounddown(fifo_entries, st->fifo_set_size); for (i = 0; i < fifo_entries; i += st->fifo_set_size) { ret = regmap_noinc_read(st->regmap, ADXL380_FIFO_DATA, &st->fifo_buf[i], From 6939484a425a87d3d8fd168f721d9cf8a0fa86dd Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Mon, 19 Jan 2026 11:23:17 +0100 Subject: [PATCH 208/297] iio: accel: adxl380: Optimize reading of FIFO entries in interrupt handler In order to minimize the time required for transferring FIFO data from the sensor to the host machine, perform the read from the FIFO in a single call to regmap_noinc_read(). This allows reading acceleration data for all 3 axes at 16 kHz sampling frequency using a 1MHz I2C bus frequency. Signed-off-by: Francesco Lavra Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl380.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/iio/accel/adxl380.c b/drivers/iio/accel/adxl380.c index a77c2323d1aa..8870d22e8cdd 100644 --- a/drivers/iio/accel/adxl380.c +++ b/drivers/iio/accel/adxl380.c @@ -967,14 +967,12 @@ static irqreturn_t adxl380_irq_handler(int irq, void *p) return IRQ_HANDLED; fifo_entries = rounddown(fifo_entries, st->fifo_set_size); - for (i = 0; i < fifo_entries; i += st->fifo_set_size) { - ret = regmap_noinc_read(st->regmap, ADXL380_FIFO_DATA, - &st->fifo_buf[i], - 2 * st->fifo_set_size); - if (ret) - return IRQ_HANDLED; + ret = regmap_noinc_read(st->regmap, ADXL380_FIFO_DATA, &st->fifo_buf, + sizeof(*st->fifo_buf) * fifo_entries); + if (ret) + return IRQ_HANDLED; + for (i = 0; i < fifo_entries; i += st->fifo_set_size) iio_push_to_buffers(indio_dev, &st->fifo_buf[i]); - } return IRQ_HANDLED; } From b010880b9936da14f8035585ab57577aa05be23a Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Thu, 22 Jan 2026 17:34:25 +0200 Subject: [PATCH 209/297] drivers: iio: mpu3050: use dev_err_probe for regulator request Regulator requesting may result in deferred probing error which will abort driver probing. To avoid this just use dev_err_probe which handles deferred probing. Fixes: 3904b28efb2c ("iio: gyro: Add driver for the MPU-3050 gyroscope") Signed-off-by: Svyatoslav Ryhel Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/gyro/mpu3050-core.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/iio/gyro/mpu3050-core.c b/drivers/iio/gyro/mpu3050-core.c index 67ae7d1012bc..ee2fcd20545d 100644 --- a/drivers/iio/gyro/mpu3050-core.c +++ b/drivers/iio/gyro/mpu3050-core.c @@ -1162,10 +1162,8 @@ int mpu3050_common_probe(struct device *dev, mpu3050->regs[1].supply = mpu3050_reg_vlogic; ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(mpu3050->regs), mpu3050->regs); - if (ret) { - dev_err(dev, "Cannot get regulators\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "Cannot get regulators\n"); ret = mpu3050_power_up(mpu3050); if (ret) From 2f7bc8f01a9ba4ba4b1521e53a8c23b21a4c04e8 Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Mon, 19 Jan 2026 12:24:23 +0100 Subject: [PATCH 210/297] dt-bindings: iio: dac: Add max22007 Devicetree bindings for MAX22007 4-channel 12-bit DAC that drives a voltage or current output on each channel Reviewed-by: Krzysztof Kozlowski Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- .../bindings/iio/dac/adi,max22007.yaml | 120 ++++++++++++++++++ MAINTAINERS | 7 + 2 files changed, 127 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/dac/adi,max22007.yaml diff --git a/Documentation/devicetree/bindings/iio/dac/adi,max22007.yaml b/Documentation/devicetree/bindings/iio/dac/adi,max22007.yaml new file mode 100644 index 000000000000..93d95f6b4c08 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/dac/adi,max22007.yaml @@ -0,0 +1,120 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/dac/adi,max22007.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices MAX22007 DAC + +maintainers: + - Janani Sunil + +description: + The MAX22007 is a quad-channel, 12-bit digital-to-analog converter (DAC) + with integrated precision output amplifiers and current output capability. + Each channel can be independently configured for voltage or current output. + Datasheet available at https://www.analog.com/en/products/max22007.html + +$ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + const: adi,max22007 + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 500000 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + vdd-supply: + description: Low-Voltage Power Supply from +2.7V to +5.5V. + + hvdd-supply: + description: + Positive High-Voltage Power Supply from +8V to (HVSS +24V) for + the Output Channels. + + hvss-supply: + description: + Optional Negative High-Voltage Power Supply from -2V to 0V for the Output + Channels. For most applications HVSS can be connected to GND (0V), but for + applications requiring output down to true 0V or 0mA, connect to a -2V supply. + + reset-gpios: + maxItems: 1 + description: + Active low GPIO. + +patternProperties: + "^channel@[0-3]$": + $ref: /schemas/iio/dac/dac.yaml# + type: object + description: + Represents the external channels which are connected to the DAC. + + properties: + reg: + description: Channel number + items: + minimum: 0 + maximum: 3 + + adi,ch-func: + description: + Channel output type. Use CH_FUNC_VOLTAGE_OUTPUT for voltage + output or CH_FUNC_CURRENT_OUTPUT for current output. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [1, 2] + + required: + - reg + - adi,ch-func + + unevaluatedProperties: false + +required: + - compatible + - reg + - vdd-supply + - hvdd-supply + +unevaluatedProperties: false + +examples: + - | + #include + #include + + spi { + #address-cells = <1>; + #size-cells = <0>; + + dac@0 { + compatible = "adi,max22007"; + reg = <0>; + spi-max-frequency = <500000>; + reset-gpios = <&gpio 19 GPIO_ACTIVE_LOW>; + vdd-supply = <&vdd_reg>; + hvdd-supply = <&hvdd_reg>; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + adi,ch-func = ; + }; + + channel@1 { + reg = <1>; + adi,ch-func = ; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 4dd9f758a871..19a9140af9b7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1610,6 +1610,13 @@ W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/dac/adi,ad9739a.yaml F: drivers/iio/dac/ad9739a.c +ANALOG DEVICES INC MAX22007 DRIVER +M: Janani Sunil +L: linux-iio@vger.kernel.org +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/dac/adi,max22007.yaml + ANALOG DEVICES INC ADA4250 DRIVER M: Antoniu Miclaus L: linux-iio@vger.kernel.org From f52690c50893ef1504990199c8a2dfbb869f38c6 Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Mon, 19 Jan 2026 12:24:24 +0100 Subject: [PATCH 211/297] iio: dac: Add MAX22007 DAC driver support Add support for the MAX22007 4 channel DAC that drives a voltage or current output on each channel. Signed-off-by: Janani Sunil Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/dac/Kconfig | 13 + drivers/iio/dac/Makefile | 1 + drivers/iio/dac/max22007.c | 491 +++++++++++++++++++++++++++++++++++++ 4 files changed, 506 insertions(+) create mode 100644 drivers/iio/dac/max22007.c diff --git a/MAINTAINERS b/MAINTAINERS index 19a9140af9b7..70163f939602 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1616,6 +1616,7 @@ L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/dac/adi,max22007.yaml +F: drivers/iio/dac/max22007.c ANALOG DEVICES INC ADA4250 DRIVER M: Antoniu Miclaus diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index e47d73300b07..db9f5c711b3d 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -482,6 +482,19 @@ config MAX517 This driver can also be built as a module. If so, the module will be called max517. +config MAX22007 + tristate "Analog Devices MAX22007 DAC Driver" + depends on SPI + select REGMAP_SPI + select CRC8 + help + Say Y here if you want to build a driver for Analog Devices MAX22007. + + MAX22007 is a quad-channel, 12-bit, voltage-output digital to + analog converter (DAC) with SPI interface. + + If compiled as a module, it will be called max22007. + config MAX5522 tristate "Maxim MAX5522 DAC driver" depends on SPI_MASTER diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index 9443e3c71c79..2a80bbf4e80a 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -48,6 +48,7 @@ obj-$(CONFIG_LTC2664) += ltc2664.o obj-$(CONFIG_LTC2688) += ltc2688.o obj-$(CONFIG_M62332) += m62332.o obj-$(CONFIG_MAX517) += max517.o +obj-$(CONFIG_MAX22007) += max22007.o obj-$(CONFIG_MAX5522) += max5522.o obj-$(CONFIG_MAX5821) += max5821.o obj-$(CONFIG_MCP4725) += mcp4725.o diff --git a/drivers/iio/dac/max22007.c b/drivers/iio/dac/max22007.c new file mode 100644 index 000000000000..182ac7155a89 --- /dev/null +++ b/drivers/iio/dac/max22007.c @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * max22007.c - MAX22007 DAC driver + * + * Driver for Analog Devices MAX22007 Digital to Analog Converter. + * + * Copyright (c) 2026 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +struct device; + +#define MAX22007_NUM_CHANNELS 4 +#define MAX22007_REV_ID_REG 0x00 +#define MAX22007_STAT_INTR_REG 0x01 +#define MAX22007_INTERRUPT_EN_REG 0x02 +#define MAX22007_CONFIG_REG 0x03 +#define MAX22007_CONTROL_REG 0x04 +#define MAX22007_CHANNEL_MODE_REG 0x05 +#define MAX22007_SOFT_RESET_REG 0x06 +#define MAX22007_DAC_CHANNEL_REG(ch) (0x07 + (ch)) +#define MAX22007_GPIO_CTRL_REG 0x0B +#define MAX22007_GPIO_DATA_REG 0x0C +#define MAX22007_GPI_EDGE_INT_CTRL_REG 0x0D +#define MAX22007_GPI_INT_STATUS_REG 0x0E + +/* Channel mask definitions */ +#define MAX22007_CH_MODE_CH_MASK(ch) BIT(12 + (ch)) +#define MAX22007_CH_PWRON_CH_MASK(ch) BIT(8 + (ch)) +#define MAX22007_DAC_LATCH_MODE_MASK(ch) BIT(12 + (ch)) +#define MAX22007_LDAC_UPDATE_MASK(ch) BIT(12 + (ch)) +#define MAX22007_SW_RST_MASK BIT(8) +#define MAX22007_SW_CLR_MASK BIT(12) +#define MAX22007_SOFT_RESET_BITS_MASK (MAX22007_SW_RST_MASK | \ + MAX22007_SW_CLR_MASK) +#define MAX22007_DAC_DATA_MASK GENMASK(15, 4) +#define MAX22007_DAC_MAX_RAW GENMASK(11, 0) +#define MAX22007_CRC8_POLYNOMIAL 0x8C +#define MAX22007_CRC_EN_MASK BIT(0) +#define MAX22007_RW_MASK BIT(0) +#define MAX22007_CRC_OVERHEAD 1 +#define MAX22007_NUM_SUPPLIES 3 +#define MAX22007_REF_MV 2500 + +/* Field value preparation macros with masking */ +#define MAX22007_CH_PWR_VAL(ch, val) (((val) & 0x1) << (8 + (ch))) +#define MAX22007_CH_MODE_VAL(ch, val) (((val) & 0x1) << (12 + (ch))) +#define MAX22007_DAC_LATCH_MODE_VAL(ch, val) (((val) & 0x1) << (12 + (ch))) + +static u8 max22007_crc8_table[CRC8_TABLE_SIZE]; + +static const char * const max22007_supply_names[MAX22007_NUM_SUPPLIES] = { + "vdd", + "hvdd", + "hvss", +}; + +struct max22007_state { + struct spi_device *spi; + struct regmap *regmap; + struct iio_chan_spec *iio_chans; + u8 tx_buf[4] __aligned(IIO_DMA_MINALIGN); + u8 rx_buf[4]; +}; + +static int max22007_spi_read(void *context, const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct max22007_state *st = context; + u8 calculated_crc, received_crc; + u8 rx_buf[4]; + u8 reg_byte; + int ret; + + if (reg_size != 1) + return -EINVAL; + + if (val_size == 0 || val_size > 3) + return -EINVAL; + + memcpy(®_byte, reg, 1); + + ret = spi_write_then_read(st->spi, ®_byte, 1, rx_buf, + val_size + MAX22007_CRC_OVERHEAD); + if (ret) { + dev_err(&st->spi->dev, "SPI transfer failed: %d\n", ret); + return ret; + } + + calculated_crc = crc8(max22007_crc8_table, ®_byte, 1, 0x00); + calculated_crc = crc8(max22007_crc8_table, rx_buf, 2, calculated_crc); + received_crc = rx_buf[val_size]; + + if (calculated_crc != received_crc) { + dev_err(&st->spi->dev, "CRC mismatch on read register %02x\n", reg_byte); + return -EIO; + } + + memcpy(val, rx_buf, val_size); + + return 0; +} + +static int max22007_spi_write(void *context, const void *data, size_t count) +{ + struct max22007_state *st = context; + struct spi_transfer xfer = { + .tx_buf = st->tx_buf, + .rx_buf = st->rx_buf, + }; + + if (count + MAX22007_CRC_OVERHEAD > sizeof(st->tx_buf)) + return -EINVAL; + + memset(st->tx_buf, 0, sizeof(st->tx_buf)); + + xfer.len = count + MAX22007_CRC_OVERHEAD; + + memcpy(st->tx_buf, data, count); + st->tx_buf[count] = crc8(max22007_crc8_table, st->tx_buf, + sizeof(st->tx_buf) - 1, 0x00); + + return spi_sync_transfer(st->spi, &xfer, 1); +} + +static bool max22007_reg_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX22007_REV_ID_REG: + case MAX22007_STAT_INTR_REG: + case MAX22007_CONFIG_REG: + case MAX22007_CONTROL_REG: + case MAX22007_CHANNEL_MODE_REG: + case MAX22007_SOFT_RESET_REG: + case MAX22007_GPIO_CTRL_REG: + case MAX22007_GPIO_DATA_REG: + case MAX22007_GPI_EDGE_INT_CTRL_REG: + case MAX22007_GPI_INT_STATUS_REG: + return true; + case MAX22007_DAC_CHANNEL_REG(0) ... MAX22007_DAC_CHANNEL_REG(MAX22007_NUM_CHANNELS - 1): + return true; + default: + return false; + } +} + +static bool max22007_reg_writable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX22007_CONFIG_REG: + case MAX22007_CONTROL_REG: + case MAX22007_CHANNEL_MODE_REG: + case MAX22007_SOFT_RESET_REG: + case MAX22007_GPIO_CTRL_REG: + case MAX22007_GPIO_DATA_REG: + case MAX22007_GPI_EDGE_INT_CTRL_REG: + return true; + case MAX22007_DAC_CHANNEL_REG(0) ... MAX22007_DAC_CHANNEL_REG(MAX22007_NUM_CHANNELS - 1): + return true; + default: + return false; + } +} + +static const struct regmap_bus max22007_regmap_bus = { + .read = max22007_spi_read, + .write = max22007_spi_write, + .read_flag_mask = MAX22007_RW_MASK, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +static const struct regmap_config max22007_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .reg_shift = -1, + .readable_reg = max22007_reg_readable, + .writeable_reg = max22007_reg_writable, + .max_register = 0x0E, +}; + +static int max22007_write_channel_data(struct max22007_state *st, + unsigned int channel, int data) +{ + unsigned int reg_val; + + if (data < 0 || data > MAX22007_DAC_MAX_RAW) + return -EINVAL; + + reg_val = FIELD_PREP(MAX22007_DAC_DATA_MASK, data); + + return regmap_write(st->regmap, MAX22007_DAC_CHANNEL_REG(channel), reg_val); +} + +static int max22007_read_channel_data(struct max22007_state *st, + unsigned int channel, int *data) +{ + unsigned int reg_val; + int ret; + + ret = regmap_read(st->regmap, MAX22007_DAC_CHANNEL_REG(channel), ®_val); + if (ret) + return ret; + + *data = FIELD_GET(MAX22007_DAC_DATA_MASK, reg_val); + + return 0; +} + +static int max22007_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max22007_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = max22007_read_channel_data(st, chan->channel, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_VOLTAGE) + *val = 5 * MAX22007_REF_MV; /* 5 * Vref in mV */ + else + *val = 25; /* Vref / (2 * Rsense) = MAX22007_REF_MV / 100 */ + *val2 = 12; /* 12-bit DAC resolution */ + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int max22007_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct max22007_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return max22007_write_channel_data(st, chan->channel, val); + default: + return -EINVAL; + } +} + +static const struct iio_info max22007_info = { + .read_raw = max22007_read_raw, + .write_raw = max22007_write_raw, +}; + +static ssize_t max22007_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct max22007_state *st = iio_priv(indio_dev); + unsigned int reg_val; + bool powerdown; + int ret; + + ret = regmap_read(st->regmap, MAX22007_CHANNEL_MODE_REG, ®_val); + if (ret) + return ret; + + powerdown = !(reg_val & MAX22007_CH_PWRON_CH_MASK(chan->channel)); + + return sysfs_emit(buf, "%d\n", powerdown); +} + +static ssize_t max22007_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct max22007_state *st = iio_priv(indio_dev); + bool powerdown; + int ret; + + ret = kstrtobool(buf, &powerdown); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, MAX22007_CHANNEL_MODE_REG, + MAX22007_CH_PWRON_CH_MASK(chan->channel), + MAX22007_CH_PWR_VAL(chan->channel, powerdown ? 0 : 1)); + if (ret) + return ret; + + return len; +} + +static const struct iio_chan_spec_ext_info max22007_ext_info[] = { + { + .name = "powerdown", + .read = max22007_read_dac_powerdown, + .write = max22007_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + { } +}; + +static int max22007_parse_channel_cfg(struct max22007_state *st, u8 *num_channels) +{ + struct device *dev = &st->spi->dev; + int ret, num_chan; + int i = 0; + u32 reg; + + num_chan = device_get_child_node_count(dev); + if (!num_chan) + return dev_err_probe(dev, -ENODEV, "no channels configured\n"); + + st->iio_chans = devm_kcalloc(dev, num_chan, sizeof(*st->iio_chans), GFP_KERNEL); + if (!st->iio_chans) + return -ENOMEM; + + device_for_each_child_node_scoped(dev, child) { + u32 ch_func; + enum iio_chan_type chan_type; + + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + return dev_err_probe(dev, ret, + "failed to read reg property of %pfwP\n", child); + + if (reg >= MAX22007_NUM_CHANNELS) + return dev_err_probe(dev, -EINVAL, + "reg out of range in %pfwP\n", child); + + ret = fwnode_property_read_u32(child, "adi,ch-func", &ch_func); + if (ret) + return dev_err_probe(dev, ret, + "missing adi,ch-func property for %pfwP\n", child); + + switch (ch_func) { + case CH_FUNC_VOLTAGE_OUTPUT: + chan_type = IIO_VOLTAGE; + break; + case CH_FUNC_CURRENT_OUTPUT: + chan_type = IIO_CURRENT; + break; + default: + return dev_err_probe(dev, -EINVAL, + "invalid adi,ch-func %u for %pfwP\n", + ch_func, child); + } + + st->iio_chans[i++] = (struct iio_chan_spec) { + .output = 1, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .ext_info = max22007_ext_info, + .channel = reg, + .type = chan_type, + }; + + ret = regmap_update_bits(st->regmap, MAX22007_CHANNEL_MODE_REG, + MAX22007_CH_MODE_CH_MASK(reg), + MAX22007_CH_MODE_VAL(reg, ch_func - 1)); + if (ret) + return ret; + + /* Set DAC to transparent mode (immediate update) */ + ret = regmap_update_bits(st->regmap, MAX22007_CONFIG_REG, + MAX22007_DAC_LATCH_MODE_MASK(reg), + MAX22007_DAC_LATCH_MODE_VAL(reg, 1)); + if (ret) + return ret; + } + + *num_channels = num_chan; + + return 0; +} + +static int max22007_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct gpio_desc *reset_gpio; + struct max22007_state *st; + struct iio_dev *indio_dev; + u8 num_channels; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + + crc8_populate_lsb(max22007_crc8_table, MAX22007_CRC8_POLYNOMIAL); + + st->regmap = devm_regmap_init(dev, &max22007_regmap_bus, st, + &max22007_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "Failed to initialize regmap\n"); + + ret = devm_regulator_bulk_get_enable(dev, MAX22007_NUM_SUPPLIES, + max22007_supply_names); + if (ret) + return dev_err_probe(dev, ret, "Failed to get and enable regulators\n"); + + reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(reset_gpio)) + return dev_err_probe(dev, PTR_ERR(reset_gpio), + "Failed to get reset GPIO\n"); + + if (reset_gpio) { + gpiod_set_value_cansleep(reset_gpio, 1); + usleep_range(1000, 5000); + gpiod_set_value_cansleep(reset_gpio, 0); + usleep_range(1000, 5000); + } else { + ret = regmap_write(st->regmap, MAX22007_SOFT_RESET_REG, + MAX22007_SOFT_RESET_BITS_MASK); + if (ret) + return ret; + } + + ret = regmap_set_bits(st->regmap, MAX22007_CONFIG_REG, + MAX22007_CRC_EN_MASK); + if (ret) + return ret; + + ret = max22007_parse_channel_cfg(st, &num_channels); + if (ret) + return ret; + + indio_dev->info = &max22007_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->iio_chans; + indio_dev->num_channels = num_channels; + indio_dev->name = "max22007"; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id max22007_id[] = { + { "max22007" }, + { } +}; +MODULE_DEVICE_TABLE(spi, max22007_id); + +static const struct of_device_id max22007_of_match[] = { + { .compatible = "adi,max22007" }, + { } +}; +MODULE_DEVICE_TABLE(of, max22007_of_match); + +static struct spi_driver max22007_driver = { + .driver = { + .name = "max22007", + .of_match_table = max22007_of_match, + }, + .probe = max22007_probe, + .id_table = max22007_id, +}; +module_spi_driver(max22007_driver); + +MODULE_AUTHOR("Janani Sunil "); +MODULE_DESCRIPTION("Analog Devices MAX22007 DAC"); +MODULE_LICENSE("GPL"); From 5abb6c7aca41d827320113b6865f56d2038a4f2c Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Fri, 23 Jan 2026 10:20:29 -0800 Subject: [PATCH 212/297] dt-bindings: spmi: Add MediaTek MT8196 SPMI 2 Arbiter/Controllers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document the MT8196 SPMI 2.0 Controller with a new schema. This is a MIPI SPMI 2.0 compliant IP, composed of a main arbiter and two SPMI master controllers with Request Capable Slave (RCS) support. Reviewed-by: Rob Herring (Arm) Reviewed-by: Nícolas F. R. A. Prado Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Stephen Boyd Link: https://patch.msgid.link/20260123182039.224314-2-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../bindings/spmi/mediatek,mt8196-spmi.yaml | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 Documentation/devicetree/bindings/spmi/mediatek,mt8196-spmi.yaml diff --git a/Documentation/devicetree/bindings/spmi/mediatek,mt8196-spmi.yaml b/Documentation/devicetree/bindings/spmi/mediatek,mt8196-spmi.yaml new file mode 100644 index 000000000000..7a534f0a1d87 --- /dev/null +++ b/Documentation/devicetree/bindings/spmi/mediatek,mt8196-spmi.yaml @@ -0,0 +1,138 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spmi/mediatek,mt8196-spmi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek MT8196 SPMI 2.0 Controller + +maintainers: + - Hsin-Hsiung Wang + - AngeloGioacchino Del Regno + +description: + The MediaTek MT8196 SoC features a SPMI version 2.0 compliant controller, + with internal wrapping arbitration logic to allow for multiple on-chip + devices to control up to two SPMI buses. + The main arbiter also acts as an interrupt controller, arbitering also + the interrupts coming from SPMI-connected devices into each of the nested + interrupt controllers from any of the present SPMI buses. + +properties: + compatible: + oneOf: + - enum: + - mediatek,mt8196-spmi + - items: + - enum: + - mediatek,mt6991-spmi + - const: mediatek,mt8196-spmi + + ranges: true + + '#address-cells': + const: 1 + + '#size-cells': + const: 1 + +patternProperties: + "^spmi@[a-f0-9]+$": + type: object + $ref: /schemas/spmi/spmi.yaml + unevaluatedProperties: false + + properties: + reg: + items: + - description: controller interface registers + - description: spmi master controller registers + + reg-names: + items: + - const: pmif + - const: spmimst + + clocks: + items: + - description: controller interface system clock + - description: controller interface timer clock + - description: spmi controller master clock + + clock-names: + items: + - const: pmif_sys_ck + - const: pmif_tmr_ck + - const: spmimst_clk_mux + + interrupts: + maxItems: 1 + + interrupt-names: + const: rcs + + interrupt-controller: true + + "#interrupt-cells": + const: 3 + description: | + cell 1: slave ID for the requested interrupt (0-15) + cell 2: the requested peripheral interrupt (0-7) + cell 3: interrupt flags indicating level-sense information, + as defined in dt-bindings/interrupt-controller/irq.h + required: + - reg + - reg-names + - clocks + - clock-names + - interrupts + - interrupt-names + - interrupt-controller + - "#interrupt-cells" + +required: + - compatible + - ranges + - '#address-cells' + - '#size-cells' + +additionalProperties: false + +examples: + - | + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + spmi-arbiter@1c018000 { + compatible = "mediatek,mt8196-spmi"; + ranges = <0 0 0x1c018000 0x4900>; + #address-cells = <1>; + #size-cells = <1>; + + spmi@0 { + reg = <0 0x900>, <0x4800 0x100>; + reg-names = "pmif", "spmimst"; + interrupts-extended = <&pio 292 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "rcs"; + interrupt-controller; + #interrupt-cells = <3>; + clocks = <&pmif_sys>, <&pmif_tmr>, <&spmi_mst>; + clock-names = "pmif_sys_ck", "pmif_tmr_ck", "spmimst_clk_mux"; + }; + + spmi@2000 { + reg = <0x2000 0x900>, <0x4000 0x100>; + reg-names = "pmif", "spmimst"; + interrupts-extended = <&pio 291 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "rcs"; + interrupt-controller; + #interrupt-cells = <3>; + clocks = <&pmif_sys>, <&pmif_tmr>, <&spmi_mst>; + clock-names = "pmif_sys_ck", "pmif_tmr_ck", "spmimst_clk_mux"; + }; + }; + }; +... From 078117963b2c678921c86f2485b0d5baf5b86131 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Fri, 23 Jan 2026 10:20:30 -0800 Subject: [PATCH 213/297] spmi: mtk-pmif: Add multi-bus support for SPMI 2.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In preparation for adding support for MT8196/MT6991 SoCs having multiple SPMI busses, move the bus specific parameters into a new pmif_bus structure and keep the SoC-specific data in the already existing struct pmif, and add means to register multiple SPMI controllers. While this needs a different devicetree node structure, where each of the controllers are in subnodes of a main SPMI node, and where each has its own resources (iospaces and clocks), support for the legacy single-controller was retained and doesn't require any DT change in the currently supported SoCs. Reviewed-by: Nícolas F. R. A. Prado Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Stephen Boyd Link: https://patch.msgid.link/20260123182039.224314-3-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/spmi/spmi-mtk-pmif.c | 249 +++++++++++++++++++++++------------ 1 file changed, 164 insertions(+), 85 deletions(-) diff --git a/drivers/spmi/spmi-mtk-pmif.c b/drivers/spmi/spmi-mtk-pmif.c index 160d36f7d238..68f458587c67 100644 --- a/drivers/spmi/spmi-mtk-pmif.c +++ b/drivers/spmi/spmi-mtk-pmif.c @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0 // // Copyright (c) 2021 MediaTek Inc. +// Copyright (c) 2025 Collabora Ltd +// AngeloGioacchino Del Regno #include #include @@ -25,6 +27,7 @@ #define PMIF_CHAN_OFFSET 0x5 +#define PMIF_MAX_BUSES 2 #define PMIF_MAX_CLKS 3 #define SPMI_OP_ST_BUSY 1 @@ -41,16 +44,22 @@ struct pmif_data { const u32 *regs; const u32 *spmimst_regs; u32 soc_chan; + u32 num_spmi_buses; +}; + +struct pmif_bus { + void __iomem *base; + void __iomem *spmimst_base; + struct spmi_controller *ctrl; + struct clk_bulk_data clks[PMIF_MAX_CLKS]; + size_t nclks; + raw_spinlock_t lock; }; struct pmif { - void __iomem *base; - void __iomem *spmimst_base; + struct pmif_bus bus[PMIF_MAX_BUSES]; struct ch_reg chan; - struct clk_bulk_data clks[PMIF_MAX_CLKS]; - size_t nclks; const struct pmif_data *data; - raw_spinlock_t lock; }; static const char * const pmif_clock_names[] = { @@ -262,33 +271,41 @@ static const u32 mt8195_spmi_regs[] = { [SPMI_MST_DBG] = 0x00FC, }; -static u32 pmif_readl(struct pmif *arb, enum pmif_regs reg) +static inline struct pmif *to_mtk_pmif(struct spmi_controller *ctrl) { - return readl(arb->base + arb->data->regs[reg]); + return dev_get_drvdata(ctrl->dev.parent); } -static void pmif_writel(struct pmif *arb, u32 val, enum pmif_regs reg) +static u32 pmif_readl(struct pmif *arb, struct pmif_bus *pbus, enum pmif_regs reg) { - writel(val, arb->base + arb->data->regs[reg]); + return readl(pbus->base + arb->data->regs[reg]); } -static void mtk_spmi_writel(struct pmif *arb, u32 val, enum spmi_regs reg) +static void pmif_writel(struct pmif *arb, struct pmif_bus *pbus, + u32 val, enum pmif_regs reg) { - writel(val, arb->spmimst_base + arb->data->spmimst_regs[reg]); + writel(val, pbus->base + arb->data->regs[reg]); } -static bool pmif_is_fsm_vldclr(struct pmif *arb) +static void mtk_spmi_writel(struct pmif *arb, struct pmif_bus *pbus, + u32 val, enum spmi_regs reg) +{ + writel(val, pbus->spmimst_base + arb->data->spmimst_regs[reg]); +} + +static bool pmif_is_fsm_vldclr(struct pmif *arb, struct pmif_bus *pbus) { u32 reg_rdata; - reg_rdata = pmif_readl(arb, arb->chan.ch_sta); + reg_rdata = pmif_readl(arb, pbus, arb->chan.ch_sta); return GET_SWINF(reg_rdata) == SWINF_WFVLDCLR; } static int pmif_arb_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid) { - struct pmif *arb = spmi_controller_get_drvdata(ctrl); + struct pmif_bus *pbus = spmi_controller_get_drvdata(ctrl); + struct pmif *arb = to_mtk_pmif(ctrl); u32 rdata, cmd; int ret; @@ -298,8 +315,8 @@ static int pmif_arb_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid) cmd = opc - SPMI_CMD_RESET; - mtk_spmi_writel(arb, (cmd << 0x4) | sid, SPMI_OP_ST_CTRL); - ret = readl_poll_timeout_atomic(arb->spmimst_base + arb->data->spmimst_regs[SPMI_OP_ST_STA], + mtk_spmi_writel(arb, pbus, (cmd << 0x4) | sid, SPMI_OP_ST_CTRL); + ret = readl_poll_timeout_atomic(pbus->spmimst_base + arb->data->spmimst_regs[SPMI_OP_ST_STA], rdata, (rdata & SPMI_OP_ST_BUSY) == SPMI_OP_ST_BUSY, PMIF_DELAY_US, PMIF_TIMEOUT_US); if (ret < 0) @@ -311,7 +328,8 @@ static int pmif_arb_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid) static int pmif_spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, u16 addr, u8 *buf, size_t len) { - struct pmif *arb = spmi_controller_get_drvdata(ctrl); + struct pmif_bus *pbus = spmi_controller_get_drvdata(ctrl); + struct pmif *arb = to_mtk_pmif(ctrl); struct ch_reg *inf_reg; int ret; u32 data, cmd; @@ -336,31 +354,31 @@ static int pmif_spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, else return -EINVAL; - raw_spin_lock_irqsave(&arb->lock, flags); + raw_spin_lock_irqsave(&pbus->lock, flags); /* Wait for Software Interface FSM state to be IDLE. */ inf_reg = &arb->chan; - ret = readl_poll_timeout_atomic(arb->base + arb->data->regs[inf_reg->ch_sta], + ret = readl_poll_timeout_atomic(pbus->base + arb->data->regs[inf_reg->ch_sta], data, GET_SWINF(data) == SWINF_IDLE, PMIF_DELAY_US, PMIF_TIMEOUT_US); if (ret < 0) { /* set channel ready if the data has transferred */ - if (pmif_is_fsm_vldclr(arb)) - pmif_writel(arb, 1, inf_reg->ch_rdy); - raw_spin_unlock_irqrestore(&arb->lock, flags); + if (pmif_is_fsm_vldclr(arb, pbus)) + pmif_writel(arb, pbus, 1, inf_reg->ch_rdy); + raw_spin_unlock_irqrestore(&pbus->lock, flags); dev_err(&ctrl->dev, "failed to wait for SWINF_IDLE\n"); return ret; } /* Send the command. */ cmd = (opc << 30) | (sid << 24) | ((len - 1) << 16) | addr; - pmif_writel(arb, cmd, inf_reg->ch_send); - raw_spin_unlock_irqrestore(&arb->lock, flags); + pmif_writel(arb, pbus, cmd, inf_reg->ch_send); + raw_spin_unlock_irqrestore(&pbus->lock, flags); /* * Wait for Software Interface FSM state to be WFVLDCLR, * read the data and clear the valid flag. */ - ret = readl_poll_timeout_atomic(arb->base + arb->data->regs[inf_reg->ch_sta], + ret = readl_poll_timeout_atomic(pbus->base + arb->data->regs[inf_reg->ch_sta], data, GET_SWINF(data) == SWINF_WFVLDCLR, PMIF_DELAY_US, PMIF_TIMEOUT_US); if (ret < 0) { @@ -368,9 +386,9 @@ static int pmif_spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, return ret; } - data = pmif_readl(arb, inf_reg->rdata); + data = pmif_readl(arb, pbus, inf_reg->rdata); memcpy(buf, &data, len); - pmif_writel(arb, 1, inf_reg->ch_rdy); + pmif_writel(arb, pbus, 1, inf_reg->ch_rdy); return 0; } @@ -378,7 +396,8 @@ static int pmif_spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, static int pmif_spmi_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, u16 addr, const u8 *buf, size_t len) { - struct pmif *arb = spmi_controller_get_drvdata(ctrl); + struct pmif_bus *pbus = spmi_controller_get_drvdata(ctrl); + struct pmif *arb = to_mtk_pmif(ctrl); struct ch_reg *inf_reg; int ret; u32 data, wdata, cmd; @@ -409,27 +428,27 @@ static int pmif_spmi_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, /* Set the write data. */ memcpy(&wdata, buf, len); - raw_spin_lock_irqsave(&arb->lock, flags); + raw_spin_lock_irqsave(&pbus->lock, flags); /* Wait for Software Interface FSM state to be IDLE. */ inf_reg = &arb->chan; - ret = readl_poll_timeout_atomic(arb->base + arb->data->regs[inf_reg->ch_sta], + ret = readl_poll_timeout_atomic(pbus->base + arb->data->regs[inf_reg->ch_sta], data, GET_SWINF(data) == SWINF_IDLE, PMIF_DELAY_US, PMIF_TIMEOUT_US); if (ret < 0) { /* set channel ready if the data has transferred */ - if (pmif_is_fsm_vldclr(arb)) - pmif_writel(arb, 1, inf_reg->ch_rdy); - raw_spin_unlock_irqrestore(&arb->lock, flags); + if (pmif_is_fsm_vldclr(arb, pbus)) + pmif_writel(arb, pbus, 1, inf_reg->ch_rdy); + raw_spin_unlock_irqrestore(&pbus->lock, flags); dev_err(&ctrl->dev, "failed to wait for SWINF_IDLE\n"); return ret; } - pmif_writel(arb, wdata, inf_reg->wdata); + pmif_writel(arb, pbus, wdata, inf_reg->wdata); /* Send the command. */ cmd = (opc << 30) | BIT(29) | (sid << 24) | ((len - 1) << 16) | addr; - pmif_writel(arb, cmd, inf_reg->ch_send); - raw_spin_unlock_irqrestore(&arb->lock, flags); + pmif_writel(arb, pbus, cmd, inf_reg->ch_send); + raw_spin_unlock_irqrestore(&pbus->lock, flags); return 0; } @@ -446,52 +465,118 @@ static const struct pmif_data mt8195_pmif_arb = { .soc_chan = 2, }; -static int mtk_spmi_probe(struct platform_device *pdev) +static int mtk_spmi_bus_probe(struct platform_device *pdev, + struct device_node *node, + const struct pmif_data *pdata, + struct pmif_bus *pbus) { - struct pmif *arb; struct spmi_controller *ctrl; - int err, i; - u32 chan_offset; + int err, idx, bus_id, i; - ctrl = devm_spmi_controller_alloc(&pdev->dev, sizeof(*arb)); + if (pdata->num_spmi_buses > 1) + bus_id = of_alias_get_id(node, "spmi"); + else + bus_id = 0; + + if (bus_id < 0) + return dev_err_probe(&pdev->dev, bus_id, + "Cannot find SPMI Bus alias ID\n"); + + ctrl = devm_spmi_controller_alloc(&pdev->dev, sizeof(*pbus)); if (IS_ERR(ctrl)) return PTR_ERR(ctrl); - arb = spmi_controller_get_drvdata(ctrl); + pbus = spmi_controller_get_drvdata(ctrl); + pbus->ctrl = ctrl; + + idx = of_property_match_string(node, "reg-names", "pmif"); + if (idx < 0) + return -EINVAL; + + pbus->base = devm_of_iomap(&pdev->dev, node, idx, NULL); + if (IS_ERR(pbus->base)) + return PTR_ERR(pbus->base); + + idx = of_property_match_string(node, "reg-names", "spmimst"); + if (idx < 0) + return -EINVAL; + + pbus->spmimst_base = devm_of_iomap(&pdev->dev, node, idx, NULL); + if (IS_ERR(pbus->spmimst_base)) + return PTR_ERR(pbus->spmimst_base); + + pbus->nclks = ARRAY_SIZE(pmif_clock_names); + for (i = 0; i < pbus->nclks; i++) { + pbus->clks[i].id = pmif_clock_names[i]; + pbus->clks[i].clk = of_clk_get_by_name(node, pbus->clks[i].id); + if (IS_ERR(pbus->clks[i].clk)) + return PTR_ERR(pbus->clks[i].clk); + } + + err = clk_bulk_prepare_enable(pbus->nclks, pbus->clks); + if (err) + goto err_put_clks; + + ctrl->cmd = pmif_arb_cmd; + ctrl->read_cmd = pmif_spmi_read_cmd; + ctrl->write_cmd = pmif_spmi_write_cmd; + ctrl->dev.of_node = node; + dev_set_name(&ctrl->dev, "spmi-%d", bus_id); + + raw_spin_lock_init(&pbus->lock); + + err = spmi_controller_add(ctrl); + if (err) + goto err_domain_remove; + + pbus->ctrl = ctrl; + + return 0; + +err_domain_remove: + clk_bulk_disable_unprepare(pbus->nclks, pbus->clks); +err_put_clks: + clk_bulk_put(pbus->nclks, pbus->clks); + return err; +} + +static int mtk_spmi_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct pmif *arb; + u32 chan_offset; + u8 cur_bus = 0; + int ret; + + arb = devm_kzalloc(&pdev->dev, sizeof(*arb), GFP_KERNEL); + if (!arb) + return -ENOMEM; + arb->data = device_get_match_data(&pdev->dev); if (!arb->data) { dev_err(&pdev->dev, "Cannot get drv_data\n"); return -EINVAL; } - arb->base = devm_platform_ioremap_resource_byname(pdev, "pmif"); - if (IS_ERR(arb->base)) - return PTR_ERR(arb->base); + platform_set_drvdata(pdev, arb); - arb->spmimst_base = devm_platform_ioremap_resource_byname(pdev, "spmimst"); - if (IS_ERR(arb->spmimst_base)) - return PTR_ERR(arb->spmimst_base); + if (!arb->data->num_spmi_buses) { + ret = mtk_spmi_bus_probe(pdev, node, arb->data, &arb->bus[cur_bus]); + if (ret) + return ret; + } else { + for_each_available_child_of_node_scoped(node, child) { + if (!of_node_name_eq(child, "spmi")) + continue; - arb->nclks = ARRAY_SIZE(pmif_clock_names); - for (i = 0; i < arb->nclks; i++) - arb->clks[i].id = pmif_clock_names[i]; - - err = clk_bulk_get(&pdev->dev, arb->nclks, arb->clks); - if (err) { - dev_err(&pdev->dev, "Failed to get clocks: %d\n", err); - return err; + ret = mtk_spmi_bus_probe(pdev, child, arb->data, + &arb->bus[cur_bus]); + if (ret) + return ret; + cur_bus++; + } } - err = clk_bulk_prepare_enable(arb->nclks, arb->clks); - if (err) { - dev_err(&pdev->dev, "Failed to enable clocks: %d\n", err); - goto err_put_clks; - } - - ctrl->cmd = pmif_arb_cmd; - ctrl->read_cmd = pmif_spmi_read_cmd; - ctrl->write_cmd = pmif_spmi_write_cmd; - chan_offset = PMIF_CHAN_OFFSET * arb->data->soc_chan; arb->chan.ch_sta = PMIF_SWINF_0_STA + chan_offset; arb->chan.wdata = PMIF_SWINF_0_WDATA_31_0 + chan_offset; @@ -499,31 +584,24 @@ static int mtk_spmi_probe(struct platform_device *pdev) arb->chan.ch_send = PMIF_SWINF_0_ACC + chan_offset; arb->chan.ch_rdy = PMIF_SWINF_0_VLD_CLR + chan_offset; - raw_spin_lock_init(&arb->lock); - - platform_set_drvdata(pdev, ctrl); - - err = spmi_controller_add(ctrl); - if (err) - goto err_domain_remove; - return 0; - -err_domain_remove: - clk_bulk_disable_unprepare(arb->nclks, arb->clks); -err_put_clks: - clk_bulk_put(arb->nclks, arb->clks); - return err; } static void mtk_spmi_remove(struct platform_device *pdev) { - struct spmi_controller *ctrl = platform_get_drvdata(pdev); - struct pmif *arb = spmi_controller_get_drvdata(ctrl); + struct pmif *arb = platform_get_drvdata(pdev); + int i; - spmi_controller_remove(ctrl); - clk_bulk_disable_unprepare(arb->nclks, arb->clks); - clk_bulk_put(arb->nclks, arb->clks); + for (i = 0; i < PMIF_MAX_BUSES; i++) { + struct pmif_bus *pbus = &arb->bus[i]; + + if (!pbus->ctrl) + continue; + + spmi_controller_remove(pbus->ctrl); + clk_bulk_disable_unprepare(pbus->nclks, pbus->clks); + clk_bulk_put(pbus->nclks, pbus->clks); + } } static const struct of_device_id mtk_spmi_match_table[] = { @@ -549,6 +627,7 @@ static struct platform_driver mtk_spmi_driver = { }; module_platform_driver(mtk_spmi_driver); +MODULE_AUTHOR("AngeloGioacchino Del Regno "); MODULE_AUTHOR("Hsin-Hsiung Wang "); MODULE_DESCRIPTION("MediaTek SPMI Driver"); MODULE_LICENSE("GPL"); From 63cbabb003ba314be113700ca58ec320b6ce2b33 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Fri, 23 Jan 2026 10:20:31 -0800 Subject: [PATCH 214/297] spmi: mtk-pmif: Keep spinlock until read is fully done MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the spin unlocking to after reading the contents of the PMIF_SWINF_(x)_RDATA_31_0 register in pmif_spmi_read_cmd(): since this is the only register that we can read to get the data from all of the arbitered busses, a concurrent request for reading (especially on a busy arbiter) will show a race condition and a unexpected or corrupted value may be read. Doing the entire read sequence while spin locked guarantees that concurrent access to the arbiter doesn't happen. Fixes: f200fff8d019 ("spmi: mtk-pmif: Serialize PMIF status check and command submission") Reviewed-by: Nícolas F. R. A. Prado Reviewed-by: Chen-Yu Tsai Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Stephen Boyd Fixes: b45b3ccef8c0 ("spmi: mediatek: Add support for MT6873/8192") Link: https://patch.msgid.link/20260123182039.224314-4-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/spmi/spmi-mtk-pmif.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/spmi/spmi-mtk-pmif.c b/drivers/spmi/spmi-mtk-pmif.c index 68f458587c67..9f416b231ab8 100644 --- a/drivers/spmi/spmi-mtk-pmif.c +++ b/drivers/spmi/spmi-mtk-pmif.c @@ -22,7 +22,7 @@ #define PMIF_CMD_EXT_REG 2 #define PMIF_CMD_EXT_REG_LONG 3 -#define PMIF_DELAY_US 10 +#define PMIF_DELAY_US 2 #define PMIF_TIMEOUT_US (10 * 1000) #define PMIF_CHAN_OFFSET 0x5 @@ -372,7 +372,6 @@ static int pmif_spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, /* Send the command. */ cmd = (opc << 30) | (sid << 24) | ((len - 1) << 16) | addr; pmif_writel(arb, pbus, cmd, inf_reg->ch_send); - raw_spin_unlock_irqrestore(&pbus->lock, flags); /* * Wait for Software Interface FSM state to be WFVLDCLR, @@ -382,13 +381,16 @@ static int pmif_spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, data, GET_SWINF(data) == SWINF_WFVLDCLR, PMIF_DELAY_US, PMIF_TIMEOUT_US); if (ret < 0) { + raw_spin_unlock_irqrestore(&pbus->lock, flags); dev_err(&ctrl->dev, "failed to wait for SWINF_WFVLDCLR\n"); return ret; } data = pmif_readl(arb, pbus, inf_reg->rdata); - memcpy(buf, &data, len); pmif_writel(arb, pbus, 1, inf_reg->ch_rdy); + raw_spin_unlock_irqrestore(&pbus->lock, flags); + + memcpy(buf, &data, len); return 0; } From ab1b3469fc284ad8c7b28f881329448786bdc252 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Fri, 23 Jan 2026 10:20:32 -0800 Subject: [PATCH 215/297] spmi: mtk-pmif: Implement Request Capable Slave (RCS) interrupt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for the per-bus RCS interrupt by adding a new linear irqdomain and its irqchip. The SPMI controller will raise an interrupt when any of the SPMI connected devices' irq needs attention (whenever any interrupt fires on any SID) in one of four registers, where each register holds four sets of four bits of information about a SID interrupt. This controller's RCS interrupt status knowledge is limited to the address of the SID that raised an interrupt, but does not have any details about the devices irq numbers: as this may change with a future SPMI controller IP version, the devicetree is meant to hold three cells, where the first one is the SPMI SID interrupt number, the second one is a device interrupt number, and the third one is the irq sense type. Reviewed-by: Nícolas F. R. A. Prado Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Stephen Boyd Link: https://patch.msgid.link/20260123182039.224314-5-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/spmi/spmi-mtk-pmif.c | 232 ++++++++++++++++++++++++++++++++++- 1 file changed, 226 insertions(+), 6 deletions(-) diff --git a/drivers/spmi/spmi-mtk-pmif.c b/drivers/spmi/spmi-mtk-pmif.c index 9f416b231ab8..624611dd4849 100644 --- a/drivers/spmi/spmi-mtk-pmif.c +++ b/drivers/spmi/spmi-mtk-pmif.c @@ -5,12 +5,17 @@ // AngeloGioacchino Del Regno #include +#include #include +#include +#include #include #include +#include #include #include #include +#include #define SWINF_IDLE 0x00 #define SWINF_WFVLDCLR 0x06 @@ -26,6 +31,7 @@ #define PMIF_TIMEOUT_US (10 * 1000) #define PMIF_CHAN_OFFSET 0x5 +#define PMIF_RCS_IRQ_MASK GENMASK(7, 0) #define PMIF_MAX_BUSES 2 #define PMIF_MAX_CLKS 3 @@ -44,6 +50,7 @@ struct pmif_data { const u32 *regs; const u32 *spmimst_regs; u32 soc_chan; + u8 spmi_ver; u32 num_spmi_buses; }; @@ -51,8 +58,13 @@ struct pmif_bus { void __iomem *base; void __iomem *spmimst_base; struct spmi_controller *ctrl; + struct irq_domain *dom; + int irq; struct clk_bulk_data clks[PMIF_MAX_CLKS]; size_t nclks; + u8 irq_min_sid; + u8 irq_max_sid; + u16 irq_en; raw_spinlock_t lock; }; @@ -287,6 +299,11 @@ static void pmif_writel(struct pmif *arb, struct pmif_bus *pbus, writel(val, pbus->base + arb->data->regs[reg]); } +static u32 mtk_spmi_readl(struct pmif *arb, struct pmif_bus *pbus, enum spmi_regs reg) +{ + return readl(pbus->spmimst_base + arb->data->spmimst_regs[reg]); +} + static void mtk_spmi_writel(struct pmif *arb, struct pmif_bus *pbus, u32 val, enum spmi_regs reg) { @@ -343,7 +360,6 @@ static int pmif_spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, if (len > 4) { dev_err(&ctrl->dev, "pmif supports 1..4 bytes per trans, but:%zu requested", len); - return -EINVAL; } @@ -455,6 +471,159 @@ static int pmif_spmi_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, return 0; } +static void mtk_spmi_handle_chained_irq(struct irq_desc *desc) +{ + struct pmif_bus *pbus = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + struct pmif *arb = to_mtk_pmif(pbus->ctrl); + u8 regidx_min, regidx_max; + bool irq_handled = false; + unsigned int i; + + regidx_min = pbus->irq_min_sid / 4; + regidx_min += SPMI_SLV_3_0_EINT; + + regidx_max = pbus->irq_max_sid / 4; + regidx_max += SPMI_SLV_3_0_EINT; + + chained_irq_enter(chip, desc); + + for (i = regidx_min; i <= regidx_max; i++) { + u32 val = mtk_spmi_readl(arb, pbus, i); + + while (val) { + u8 bit = __ffs(val); + u8 bank = bit / 7; + u8 sid = ((i - SPMI_SLV_3_0_EINT) * 4) + bank; + + val &= ~(PMIF_RCS_IRQ_MASK << (8 * bank)); + + /* Check if IRQs for this SID are enabled */ + if (!(pbus->irq_en & BIT(sid))) + continue; + + generic_handle_domain_irq_safe(pbus->dom, sid); + irq_handled = true; + } + } + + if (!irq_handled) + handle_bad_irq(desc); + + chained_irq_exit(chip, desc); +} + +static void mtk_spmi_rcs_irq_eoi(struct irq_data *d) +{ + struct pmif_bus *pbus = irq_data_get_irq_chip_data(d); + struct pmif *arb = to_mtk_pmif(pbus->ctrl); + irq_hw_number_t irq = irqd_to_hwirq(d); + unsigned int reg, shift; + + /* There are four interrupts (8 bits each) per register */ + reg = SPMI_SLV_3_0_EINT + d->hwirq / 4; + shift = (irq % 4) * 8; + + mtk_spmi_writel(arb, pbus, PMIF_RCS_IRQ_MASK << shift, reg); +} + +static void mtk_spmi_rcs_irq_enable(struct irq_data *d) +{ + struct pmif_bus *pbus = irq_data_get_irq_chip_data(d); + irq_hw_number_t irq = irqd_to_hwirq(d); + + pbus->irq_en |= BIT(irq); +} + +static void mtk_spmi_rcs_irq_disable(struct irq_data *d) +{ + struct pmif_bus *pbus = irq_data_get_irq_chip_data(d); + irq_hw_number_t irq = irqd_to_hwirq(d); + + pbus->irq_en &= ~BIT(irq); +} + +static int mtk_spmi_rcs_irq_set_wake(struct irq_data *d, unsigned int on) +{ + struct pmif_bus *pbus = irq_data_get_irq_chip_data(d); + + return irq_set_irq_wake(pbus->irq, on); +} + +static const struct irq_chip mtk_spmi_rcs_irq_chip = { + .name = "spmi_rcs", + .irq_eoi = mtk_spmi_rcs_irq_eoi, + .irq_enable = mtk_spmi_rcs_irq_enable, + .irq_disable = mtk_spmi_rcs_irq_disable, + .irq_set_wake = mtk_spmi_rcs_irq_set_wake, +}; + +static int mtk_spmi_rcs_irq_translate(struct irq_domain *d, struct irq_fwspec *fwspec, + unsigned long *out_hwirq, unsigned int *out_type) +{ + struct pmif_bus *pbus = d->host_data; + struct device *dev = &pbus->ctrl->dev; + u32 *intspec = fwspec->param; + + if (intspec[0] > SPMI_MAX_SLAVE_ID) + return -EINVAL; + + /* + * The IRQ number in intspec[1] is ignored on purpose here! + * + * The controller only has knowledge of which SID raised an interrupt + * and the type of irq, but doesn't know about any device irq number, + * hence that must be read from the SPMI device's registers. + */ + *out_hwirq = intspec[0]; + *out_type = intspec[2] & IRQ_TYPE_SENSE_MASK; + + if (pbus->irq_min_sid > intspec[0]) + pbus->irq_min_sid = intspec[0]; + + if (pbus->irq_max_sid < intspec[0]) + pbus->irq_max_sid = intspec[0]; + + dev_dbg(dev, "Found SPMI IRQ %u (map: 0x%lx)\n", intspec[0], *out_hwirq); + + return 0; +} + +static struct lock_class_key mtk_spmi_rcs_irqlock_class, mtk_spmi_rcs_irqreq_class; + +static int mtk_spmi_rcs_irq_alloc(struct irq_domain *d, unsigned int virq, + unsigned int nr_irqs, void *data) +{ + struct pmif_bus *pbus = d->host_data; + struct device *dev = &pbus->ctrl->dev; + struct irq_fwspec *fwspec = data; + irq_hw_number_t hwirq; + unsigned int irqtype; + int i, ret; + + ret = mtk_spmi_rcs_irq_translate(d, fwspec, &hwirq, &irqtype); + if (ret) + return ret; + + for (i = 0; i < nr_irqs; i++) { + dev_dbg(dev, "Mapping IRQ%u (hwirq %lu) with type %u\n", + virq, hwirq, irqtype); + + irq_set_lockdep_class(virq, &mtk_spmi_rcs_irqlock_class, + &mtk_spmi_rcs_irqreq_class); + irq_domain_set_info(d, virq, hwirq, &mtk_spmi_rcs_irq_chip, + pbus, handle_level_irq, NULL, NULL); + } + + return 0; +} + +static const struct irq_domain_ops mtk_spmi_rcs_irq_domain_ops = { + .alloc = mtk_spmi_rcs_irq_alloc, + .free = irq_domain_free_irqs_common, + .translate = mtk_spmi_rcs_irq_translate, +}; + static const struct pmif_data mt6873_pmif_arb = { .regs = mt6873_regs, .spmimst_regs = mt6873_spmi_regs, @@ -467,6 +636,44 @@ static const struct pmif_data mt8195_pmif_arb = { .soc_chan = 2, }; +static int mtk_spmi_irq_init(struct device_node *node, + const struct pmif_data *pdata, + struct pmif_bus *pbus) +{ + struct pmif *arb = to_mtk_pmif(pbus->ctrl); + unsigned int i; + + /* No interrupts required for SPMI 1.x controller */ + if (pdata->spmi_ver < 2) { + pbus->dom = NULL; + return 0; + } + + pbus->irq = of_irq_get_byname(node, "rcs"); + if (pbus->irq <= 0) + return pbus->irq ? : -ENXIO; + + pbus->dom = irq_domain_create_tree(of_fwnode_handle(node), + &mtk_spmi_rcs_irq_domain_ops, pbus); + if (!pbus->dom) + return -ENOMEM; + + /* Clear possible unhandled interrupts coming from bootloader SPMI init */ + for (i = SPMI_SLV_3_0_EINT; i <= SPMI_SLV_F_C_EINT; i++) + mtk_spmi_writel(arb, pbus, GENMASK(31, 0), i); + + return 0; +} + +static void mtk_spmi_irq_remove(struct pmif_bus *pbus) +{ + if (!pbus->dom) + return; + + irq_set_chained_handler_and_data(pbus->irq, NULL, NULL); + irq_domain_remove(pbus->dom); +} + static int mtk_spmi_bus_probe(struct platform_device *pdev, struct device_node *node, const struct pmif_data *pdata, @@ -512,12 +719,21 @@ static int mtk_spmi_bus_probe(struct platform_device *pdev, pbus->clks[i].id = pmif_clock_names[i]; pbus->clks[i].clk = of_clk_get_by_name(node, pbus->clks[i].id); if (IS_ERR(pbus->clks[i].clk)) - return PTR_ERR(pbus->clks[i].clk); + return dev_err_probe(&pdev->dev, PTR_ERR(pbus->clks[i].clk), + "Failed to get clocks\n"); } err = clk_bulk_prepare_enable(pbus->nclks, pbus->clks); - if (err) + if (err) { + dev_err_probe(&pdev->dev, err, "Failed to enable clocks\n"); goto err_put_clks; + } + + err = mtk_spmi_irq_init(node, pdata, pbus); + if (err) { + dev_err_probe(&pdev->dev, err, "Cannot initialize SPMI IRQs\n"); + goto err_disable_clks; + } ctrl->cmd = pmif_arb_cmd; ctrl->read_cmd = pmif_spmi_read_cmd; @@ -529,13 +745,16 @@ static int mtk_spmi_bus_probe(struct platform_device *pdev, err = spmi_controller_add(ctrl); if (err) - goto err_domain_remove; + goto err_remove_irq; - pbus->ctrl = ctrl; + if (pbus->dom) + irq_set_chained_handler_and_data(pbus->irq, mtk_spmi_handle_chained_irq, pbus); return 0; -err_domain_remove: +err_remove_irq: + mtk_spmi_irq_remove(pbus); +err_disable_clks: clk_bulk_disable_unprepare(pbus->nclks, pbus->clks); err_put_clks: clk_bulk_put(pbus->nclks, pbus->clks); @@ -600,6 +819,7 @@ static void mtk_spmi_remove(struct platform_device *pdev) if (!pbus->ctrl) continue; + mtk_spmi_irq_remove(pbus); spmi_controller_remove(pbus->ctrl); clk_bulk_disable_unprepare(pbus->nclks, pbus->clks); clk_bulk_put(pbus->nclks, pbus->clks); From 1f5be2d7f743e5ffc6317af50276ca5508ddb5ec Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Fri, 23 Jan 2026 10:20:33 -0800 Subject: [PATCH 216/297] spmi: mtk-pmif: Add support for MT8196 SPMI Controller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for the SPMI controller found in the MT8196 SoC: this supports SPMI 2.0 and features two SPMI buses. Reviewed-by: Nícolas F. R. A. Prado Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Stephen Boyd Link: https://patch.msgid.link/20260123182039.224314-6-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/spmi/spmi-mtk-pmif.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/spmi/spmi-mtk-pmif.c b/drivers/spmi/spmi-mtk-pmif.c index 624611dd4849..1048420b5afb 100644 --- a/drivers/spmi/spmi-mtk-pmif.c +++ b/drivers/spmi/spmi-mtk-pmif.c @@ -636,6 +636,14 @@ static const struct pmif_data mt8195_pmif_arb = { .soc_chan = 2, }; +static const struct pmif_data mt8196_pmif_arb = { + .regs = mt8195_regs, + .spmimst_regs = mt8195_spmi_regs, + .soc_chan = 2, + .spmi_ver = 2, + .num_spmi_buses = 2, +}; + static int mtk_spmi_irq_init(struct device_node *node, const struct pmif_data *pdata, struct pmif_bus *pbus) @@ -833,6 +841,9 @@ static const struct of_device_id mtk_spmi_match_table[] = { }, { .compatible = "mediatek,mt8195-spmi", .data = &mt8195_pmif_arb, + }, { + .compatible = "mediatek,mt8196-spmi", + .data = &mt8196_pmif_arb, }, { /* sentinel */ }, From 6c54b0a801dd8227237ba0bf0728bb42681cf027 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 23 Jan 2026 10:20:34 -0800 Subject: [PATCH 217/297] spmi: apple: Add "apple,t8103-spmi" compatible After discussion with the devicetree maintainers we agreed to not extend lists with the generic compatible "apple,spmi" anymore [1]. Use "apple,t8103-spmi" as base compatible as it is the SoC the driver and bindings were written for. [1]: https://lore.kernel.org/asahi/12ab93b7-1fc2-4ce0-926e-c8141cfe81bf@kernel.org/ Fixes: 77ca75e80c71 ("spmi: add a spmi driver for Apple SoC") Cc: stable@vger.kernel.org Reviewed-by: Neal Gompa Signed-off-by: Janne Grunau Signed-off-by: Stephen Boyd Link: https://patch.msgid.link/20260123182039.224314-7-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/spmi/spmi-apple-controller.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/spmi/spmi-apple-controller.c b/drivers/spmi/spmi-apple-controller.c index 697b3e8bb023..87e3ee9d4f2a 100644 --- a/drivers/spmi/spmi-apple-controller.c +++ b/drivers/spmi/spmi-apple-controller.c @@ -149,6 +149,7 @@ static int apple_spmi_probe(struct platform_device *pdev) } static const struct of_device_id apple_spmi_match_table[] = { + { .compatible = "apple,t8103-spmi", }, { .compatible = "apple,spmi", }, {} }; From e9ae440c97e8f68cd7ce1dde47c04a36d8792bf0 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Fri, 23 Jan 2026 10:20:35 -0800 Subject: [PATCH 218/297] dt-bindings: spmi: split out common QCOM SPMI PMIC arbiter properties Split out the common SPMI PMIC arbiter properties for QCOM devices into a separate file so that it can be included as a reference for devices using them. This will be needed for the upcoming PMIC v8 arbiter support patch, as the v8 arbiter also uses these common properties. Reviewed-by: Rob Herring (Arm) Signed-off-by: Jishnu Prakash Signed-off-by: Stephen Boyd Link: https://patch.msgid.link/20260123182039.224314-8-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../spmi/qcom,spmi-pmic-arb-common.yaml | 35 +++++++++++++++++++ .../bindings/spmi/qcom,spmi-pmic-arb.yaml | 17 +-------- .../spmi/qcom,x1e80100-spmi-pmic-arb.yaml | 21 +++-------- 3 files changed, 40 insertions(+), 33 deletions(-) create mode 100644 Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb-common.yaml diff --git a/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb-common.yaml b/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb-common.yaml new file mode 100644 index 000000000000..8c38ed145e74 --- /dev/null +++ b/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb-common.yaml @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spmi/qcom,spmi-pmic-arb-common.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Technologies, Inc. SPMI Controller (common) + +maintainers: + - David Collins + +description: | + This defines some common properties used to define Qualcomm SPMI controllers + for PMIC arbiter. + +properties: + qcom,ee: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 5 + description: + indicates the active Execution Environment identifier + + qcom,channel: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 5 + description: + which of the PMIC Arb provided channels to use for accesses + +required: + - qcom,ee + - qcom,channel + +additionalProperties: true diff --git a/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb.yaml b/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb.yaml index 51daf1b847a9..d0c683dd5284 100644 --- a/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb.yaml +++ b/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb.yaml @@ -19,6 +19,7 @@ description: | allOf: - $ref: spmi.yaml + - $ref: qcom,spmi-pmic-arb-common.yaml properties: compatible: @@ -71,20 +72,6 @@ properties: '#size-cells': true - qcom,ee: - $ref: /schemas/types.yaml#/definitions/uint32 - minimum: 0 - maximum: 5 - description: > - indicates the active Execution Environment identifier - - qcom,channel: - $ref: /schemas/types.yaml#/definitions/uint32 - minimum: 0 - maximum: 5 - description: > - which of the PMIC Arb provided channels to use for accesses - qcom,bus-id: $ref: /schemas/types.yaml#/definitions/uint32 minimum: 0 @@ -97,8 +84,6 @@ properties: required: - compatible - reg-names - - qcom,ee - - qcom,channel unevaluatedProperties: false diff --git a/Documentation/devicetree/bindings/spmi/qcom,x1e80100-spmi-pmic-arb.yaml b/Documentation/devicetree/bindings/spmi/qcom,x1e80100-spmi-pmic-arb.yaml index 7c3cc20a80d6..08369fdd2161 100644 --- a/Documentation/devicetree/bindings/spmi/qcom,x1e80100-spmi-pmic-arb.yaml +++ b/Documentation/devicetree/bindings/spmi/qcom,x1e80100-spmi-pmic-arb.yaml @@ -17,6 +17,9 @@ description: | The PMIC Arbiter can also act as an interrupt controller, providing interrupts to slave devices. +allOf: + - $ref: qcom,spmi-pmic-arb-common.yaml + properties: compatible: oneOf: @@ -45,20 +48,6 @@ properties: '#size-cells': const: 2 - qcom,ee: - $ref: /schemas/types.yaml#/definitions/uint32 - minimum: 0 - maximum: 5 - description: > - indicates the active Execution Environment identifier - - qcom,channel: - $ref: /schemas/types.yaml#/definitions/uint32 - minimum: 0 - maximum: 5 - description: > - which of the PMIC Arb provided channels to use for accesses - patternProperties: "^spmi@[a-f0-9]+$": type: object @@ -96,10 +85,8 @@ patternProperties: required: - compatible - reg-names - - qcom,ee - - qcom,channel -additionalProperties: false +unevaluatedProperties: false examples: - | From 0914498171b9a0acc742cd4289134c418eabf095 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Fri, 23 Jan 2026 10:20:36 -0800 Subject: [PATCH 219/297] dt-bindings: spmi: add support for glymur-spmi-pmic-arb (arbiter v8) SPMI PMIC Arbiter version 8 builds upon version 7 with support for up to four SPMI buses. To achieve this, the register map was slightly rearranged. Add a new binding file and compatible string for version 8 using the name 'glymur' as the Qualcomm Technologies, Inc. Glymur SoC is the first one to use PMIC arbiter version 8. This specifies the new register ranges needed only for version 8. Also document SPMI PMIC Arbiter for Qualcomm Kaanapali SoC, by adding fallback to Glymur compatible string, as it too has version 8 functionality. Signed-off-by: David Collins Signed-off-by: Pankaj Patil Signed-off-by: Kamal Wadhwa Signed-off-by: Jingyi Wang Reviewed-by: Rob Herring (Arm) Signed-off-by: Jishnu Prakash Signed-off-by: Stephen Boyd Link: https://patch.msgid.link/20260123182039.224314-9-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../spmi/qcom,glymur-spmi-pmic-arb.yaml | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 Documentation/devicetree/bindings/spmi/qcom,glymur-spmi-pmic-arb.yaml diff --git a/Documentation/devicetree/bindings/spmi/qcom,glymur-spmi-pmic-arb.yaml b/Documentation/devicetree/bindings/spmi/qcom,glymur-spmi-pmic-arb.yaml new file mode 100644 index 000000000000..3b5005b96c6d --- /dev/null +++ b/Documentation/devicetree/bindings/spmi/qcom,glymur-spmi-pmic-arb.yaml @@ -0,0 +1,150 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spmi/qcom,glymur-spmi-pmic-arb.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Technologies, Inc. Glymur SPMI Controller (PMIC Arbiter v8) + +maintainers: + - David Collins + +description: | + The Glymur SPMI PMIC Arbiter implements HW version 8 and it's an SPMI + controller with wrapping arbitration logic to allow for multiple on-chip + devices to control up to 4 SPMI separate buses. + + The PMIC Arbiter can also act as an interrupt controller, providing interrupts + to slave devices. + +allOf: + - $ref: /schemas/spmi/qcom,spmi-pmic-arb-common.yaml + +properties: + compatible: + oneOf: + - items: + - enum: + - qcom,kaanapali-spmi-pmic-arb + - const: qcom,glymur-spmi-pmic-arb + - enum: + - qcom,glymur-spmi-pmic-arb + + reg: + items: + - description: core registers + - description: tx-channel per virtual slave registers + - description: rx-channel (called observer) per virtual slave registers + - description: channel to PMIC peripheral mapping registers + + reg-names: + items: + - const: core + - const: chnls + - const: obsrvr + - const: chnl_map + + ranges: true + + '#address-cells': + const: 2 + + '#size-cells': + const: 2 + +patternProperties: + "^spmi@[a-f0-9]+$": + type: object + $ref: /schemas/spmi/spmi.yaml + unevaluatedProperties: false + + properties: + reg: + items: + - description: configuration registers + - description: interrupt controller registers + - description: channel owner EE mapping registers + + reg-names: + items: + - const: cnfg + - const: intr + - const: chnl_owner + + interrupts: + maxItems: 1 + + interrupt-names: + const: periph_irq + + interrupt-controller: true + + '#interrupt-cells': + const: 4 + description: | + cell 1: slave ID for the requested interrupt (0-15) + cell 2: peripheral ID for requested interrupt (0-255) + cell 3: the requested peripheral interrupt (0-7) + cell 4: interrupt flags indicating level-sense information, + as defined in dt-bindings/interrupt-controller/irq.h + +required: + - compatible + - reg-names + +unevaluatedProperties: false + +examples: + - | + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + arbiter@c400000 { + compatible = "qcom,glymur-spmi-pmic-arb"; + reg = <0x0 0xc400000 0x0 0x3000>, + <0x0 0xc900000 0x0 0x400000>, + <0x0 0xc4c0000 0x0 0x400000>, + <0x0 0xc403000 0x0 0x8000>; + reg-names = "core", "chnls", "obsrvr", "chnl_map"; + + qcom,ee = <0>; + qcom,channel = <0>; + + #address-cells = <2>; + #size-cells = <2>; + ranges; + + spmi@c426000 { + reg = <0x0 0xc426000 0x0 0x4000>, + <0x0 0xc8c0000 0x0 0x10000>, + <0x0 0xc42a000 0x0 0x8000>; + reg-names = "cnfg", "intr", "chnl_owner"; + + interrupts-extended = <&pdc 1 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "periph_irq"; + interrupt-controller; + #interrupt-cells = <4>; + + #address-cells = <2>; + #size-cells = <0>; + }; + + spmi@c437000 { + reg = <0x0 0xc437000 0x0 0x4000>, + <0x0 0xc8d0000 0x0 0x10000>, + <0x0 0xc43b000 0x0 0x8000>; + reg-names = "cnfg", "intr", "chnl_owner"; + + interrupts-extended = <&pdc 3 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "periph_irq"; + interrupt-controller; + #interrupt-cells = <4>; + + #address-cells = <2>; + #size-cells = <0>; + }; + }; + }; From 815be38ad8c8503cec8b2c0c632e975692ab42e1 Mon Sep 17 00:00:00 2001 From: David Collins Date: Fri, 23 Jan 2026 10:20:37 -0800 Subject: [PATCH 220/297] spmi: spmi-pmic-arb: add support for PMIC arbiter v8 PMIC arbiter v8 supports up to 4 SPMI buses and up to 8192 PMIC peripherals. Its register map differs from v7 as several fields increased in size. Add support for PMIC arbiter version 8. Signed-off-by: David Collins Signed-off-by: Kamal Wadhwa Signed-off-by: Jishnu Prakash Reviewed-by: Konrad Dybcio Signed-off-by: Stephen Boyd Link: https://patch.msgid.link/20260123182039.224314-10-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/spmi/spmi-pmic-arb.c | 306 +++++++++++++++++++++++++++++------ 1 file changed, 256 insertions(+), 50 deletions(-) diff --git a/drivers/spmi/spmi-pmic-arb.c b/drivers/spmi/spmi-pmic-arb.c index 91581974ef84..69f8d456324a 100644 --- a/drivers/spmi/spmi-pmic-arb.c +++ b/drivers/spmi/spmi-pmic-arb.c @@ -2,6 +2,8 @@ /* * Copyright (c) 2012-2015, 2017, 2021, The Linux Foundation. All rights reserved. */ + +#include #include #include #include @@ -25,12 +27,12 @@ #define PMIC_ARB_VERSION_V3_MIN 0x30000000 #define PMIC_ARB_VERSION_V5_MIN 0x50000000 #define PMIC_ARB_VERSION_V7_MIN 0x70000000 +#define PMIC_ARB_VERSION_V8_MIN 0x80000000 #define PMIC_ARB_INT_EN 0x0004 #define PMIC_ARB_FEATURES 0x0004 #define PMIC_ARB_FEATURES_PERIPH_MASK GENMASK(10, 0) - -#define PMIC_ARB_FEATURES1 0x0008 +#define PMIC_ARB_FEATURES_V8_PERIPH_MASK GENMASK(12, 0) /* PMIC Arbiter channel registers offsets */ #define PMIC_ARB_CMD 0x00 @@ -50,9 +52,11 @@ #define SPMI_MAPPING_BIT_IS_1_RESULT(X) (((X) >> 0) & 0xFF) #define SPMI_MAPPING_TABLE_TREE_DEPTH 16 /* Maximum of 16-bits */ -#define PMIC_ARB_MAX_PPID BIT(12) /* PPID is 12bit */ +#define PMIC_ARB_MAX_PPID BIT(13) #define PMIC_ARB_APID_VALID BIT(15) -#define PMIC_ARB_CHAN_IS_IRQ_OWNER(reg) ((reg) & BIT(24)) +#define PMIC_ARB_CHAN_IS_IRQ_OWNER_MASK BIT(24) +#define PMIC_ARB_V8_CHAN_IS_IRQ_OWNER_MASK BIT(31) + #define INVALID_EE 0xFF /* Ownership Table */ @@ -96,30 +100,37 @@ enum pmic_arb_channel { PMIC_ARB_CHANNEL_OBS, }; -#define PMIC_ARB_MAX_BUSES 2 +#define PMIC_ARB_MAX_BUSES 4 /* Maximum number of support PMIC peripherals */ #define PMIC_ARB_MAX_PERIPHS 512 #define PMIC_ARB_MAX_PERIPHS_V7 1024 +#define PMIC_ARB_MAX_PERIPHS_V8 8192 #define PMIC_ARB_TIMEOUT_US 1000 #define PMIC_ARB_MAX_TRANS_BYTES (8) #define PMIC_ARB_APID_MASK 0xFF -#define PMIC_ARB_PPID_MASK 0xFFF +#define PMIC_ARB_PPID_MASK GENMASK(11, 0) +#define PMIC_ARB_V8_PPID_MASK GENMASK(12, 0) /* interrupt enable bit */ #define SPMI_PIC_ACC_ENABLE_BIT BIT(0) -#define spec_to_hwirq(slave_id, periph_id, irq_id, apid) \ - ((((slave_id) & 0xF) << 28) | \ - (((periph_id) & 0xFF) << 20) | \ - (((irq_id) & 0x7) << 16) | \ - (((apid) & 0x3FF) << 0)) +#define HWIRQ_SID_MASK GENMASK(28, 24) +#define HWIRQ_PID_MASK GENMASK(23, 16) +#define HWIRQ_IRQID_MASK GENMASK(15, 13) +#define HWIRQ_APID_MASK GENMASK(12, 0) -#define hwirq_to_sid(hwirq) (((hwirq) >> 28) & 0xF) -#define hwirq_to_per(hwirq) (((hwirq) >> 20) & 0xFF) -#define hwirq_to_irq(hwirq) (((hwirq) >> 16) & 0x7) -#define hwirq_to_apid(hwirq) (((hwirq) >> 0) & 0x3FF) +#define spec_to_hwirq(slave_id, periph_id, irq_id, apid) \ + (FIELD_PREP(HWIRQ_SID_MASK, (slave_id)) | \ + FIELD_PREP(HWIRQ_PID_MASK, (periph_id)) | \ + FIELD_PREP(HWIRQ_IRQID_MASK, (irq_id)) | \ + FIELD_PREP(HWIRQ_APID_MASK, (apid))) + +#define hwirq_to_sid(hwirq) FIELD_GET(HWIRQ_SID_MASK, (hwirq)) +#define hwirq_to_per(hwirq) FIELD_GET(HWIRQ_PID_MASK, (hwirq)) +#define hwirq_to_irq(hwirq) FIELD_GET(HWIRQ_IRQID_MASK, (hwirq)) +#define hwirq_to_apid(hwirq) FIELD_GET(HWIRQ_APID_MASK, (hwirq)) struct pmic_arb_ver_ops; @@ -138,11 +149,12 @@ struct spmi_pmic_arb; * @domain: irq domain object for PMIC IRQ domain * @intr: address of the SPMI interrupt control registers. * @cnfg: address of the PMIC Arbiter configuration registers. + * @apid_owner: on v8: address of APID owner mapping table registers * @spmic: spmi controller registered for this bus * @lock: lock to synchronize accesses. - * @base_apid: on v7: minimum APID associated with the particular SPMI - * bus instance - * @apid_count: on v5 and v7: number of APIDs associated with the + * @base_apid: on v7 and v8: minimum APID associated with the + * particular SPMI bus instance + * @apid_count: on v5, v7 and v8: number of APIDs associated with the * particular SPMI bus instance * @mapping_table: in-memory copy of PPID -> APID mapping table. * @mapping_table_valid:bitmap containing valid-only periphs @@ -159,6 +171,7 @@ struct spmi_pmic_arb_bus { struct irq_domain *domain; void __iomem *intr; void __iomem *cnfg; + void __iomem *apid_owner; struct spmi_controller *spmic; raw_spinlock_t lock; u16 base_apid; @@ -181,6 +194,7 @@ struct spmi_pmic_arb_bus { * @wr_base: on v1 "core", on v2 "chnls" register base off DT. * @core: core register base for v2 and above only (see above) * @core_size: core register base size + * @apid_map: on v8, APID mapping table register base * @channel: execution environment channel to use for accesses. * @ee: the current Execution Environment * @ver_ops: version dependent operations. @@ -193,6 +207,7 @@ struct spmi_pmic_arb { void __iomem *wr_base; void __iomem *core; resource_size_t core_size; + void __iomem *apid_map; u8 channel; u8 ee; const struct pmic_arb_ver_ops *ver_ops; @@ -206,6 +221,7 @@ struct spmi_pmic_arb { * * @ver_str: version string. * @get_core_resources: initializes the core, observer and channels + * @get_bus_resources: requests per-SPMI bus register resources * @init_apid: finds the apid base and count * @ppid_to_apid: finds the apid for a given ppid. * @non_data_cmd: on v1 issues an spmi non-data command. @@ -227,6 +243,9 @@ struct spmi_pmic_arb { struct pmic_arb_ver_ops { const char *ver_str; int (*get_core_resources)(struct platform_device *pdev, void __iomem *core); + int (*get_bus_resources)(struct platform_device *pdev, + struct device_node *node, + struct spmi_pmic_arb_bus *bus); int (*init_apid)(struct spmi_pmic_arb_bus *bus, int index); int (*ppid_to_apid)(struct spmi_pmic_arb_bus *bus, u16 ppid); /* spmi commands (read_cmd, write_cmd, cmd) functionality */ @@ -656,7 +675,7 @@ static int periph_interrupt(struct spmi_pmic_arb_bus *bus, u16 apid) unsigned int irq; u32 status, id; int handled = 0; - u8 sid = (bus->apid_data[apid].ppid >> 8) & 0xF; + u8 sid = (bus->apid_data[apid].ppid >> 8) & 0x1F; u8 per = bus->apid_data[apid].ppid & 0xFF; status = readl_relaxed(pmic_arb->ver_ops->irq_status(bus, apid)); @@ -686,7 +705,7 @@ static void pmic_arb_chained_irq(struct irq_desc *desc) int last = bus->max_apid; /* * acc_offset will be non-zero for the secondary SPMI bus instance on - * v7 controllers. + * v7 and v8 controllers. */ int acc_offset = bus->base_apid >> 5; u8 ee = pmic_arb->ee; @@ -913,7 +932,8 @@ static int qpnpint_irq_domain_translate(struct irq_domain *d, return -EINVAL; if (fwspec->param_count != 4) return -EINVAL; - if (intspec[0] > 0xF || intspec[1] > 0xFF || intspec[2] > 0x7) + if (intspec[0] > FIELD_MAX(HWIRQ_SID_MASK) || intspec[1] > FIELD_MAX(HWIRQ_PID_MASK) || + intspec[2] > FIELD_MAX(HWIRQ_IRQID_MASK)) return -EINVAL; ppid = intspec[0] << 8 | intspec[1]; @@ -1160,7 +1180,9 @@ static int pmic_arb_ppid_to_apid_v2(struct spmi_pmic_arb_bus *bus, u16 ppid) return apid_valid & ~PMIC_ARB_APID_VALID; } -static int pmic_arb_read_apid_map_v5(struct spmi_pmic_arb_bus *bus) +static int _pmic_arb_read_apid_map(struct spmi_pmic_arb_bus *bus, + void __iomem *ppid_base, unsigned long ppid_mask, + u8 ppid_shift, unsigned long irq_owner_mask) { struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; struct apid_data *apidd; @@ -1171,7 +1193,7 @@ static int pmic_arb_read_apid_map_v5(struct spmi_pmic_arb_bus *bus) /* * In order to allow multiple EEs to write to a single PPID in arbiter - * version 5 and 7, there is more than one APID mapped to each PPID. + * version 5,7 and 8, there can be more than one APID mapped to each PPID. * The owner field for each of these mappings specifies the EE which is * allowed to write to the APID. The owner of the last (highest) APID * which has the IRQ owner bit set for a given PPID will receive @@ -1183,19 +1205,30 @@ static int pmic_arb_read_apid_map_v5(struct spmi_pmic_arb_bus *bus) * APID = N to N+M-1 are assigned to the secondary bus * where N = number of APIDs supported by the primary bus and * M = number of APIDs supported by the secondary bus + * + * In arbiter version 8, the APID numbering space is divided between + * the SPMI buses according to this mapping: + * APID = 0 to N-1 --> bus 0 + * APID = N to N+M-1 --> bus 1 + * APID = N+M to N+M+P-1 --> bus 2 + * APID = N+M+P to N+M+P+Q-1 --> bus 3 + * where N = number of APIDs supported by bus 0 + * M = number of APIDs supported by bus 1 + * P = number of APIDs supported by bus 2 + * Q = number of APIDs supported by bus 3 */ + apidd = &bus->apid_data[bus->base_apid]; apid_max = bus->base_apid + bus->apid_count; for (i = bus->base_apid; i < apid_max; i++, apidd++) { offset = pmic_arb->ver_ops->apid_map_offset(i); if (offset >= pmic_arb->core_size) break; - - regval = readl_relaxed(pmic_arb->core + offset); + regval = readl_relaxed(ppid_base + offset); if (!regval) continue; - ppid = (regval >> 8) & PMIC_ARB_PPID_MASK; - is_irq_ee = PMIC_ARB_CHAN_IS_IRQ_OWNER(regval); + ppid = (regval >> ppid_shift) & ppid_mask; + is_irq_ee = regval & irq_owner_mask; regval = readl_relaxed(pmic_arb->ver_ops->apid_owner(bus, i)); apidd->write_ee = SPMI_OWNERSHIP_PERIPH2OWNER(regval); @@ -1237,6 +1270,12 @@ static int pmic_arb_read_apid_map_v5(struct spmi_pmic_arb_bus *bus) return 0; } +static int pmic_arb_read_apid_map_v5(struct spmi_pmic_arb_bus *bus) +{ + return _pmic_arb_read_apid_map(bus, bus->pmic_arb->core, PMIC_ARB_PPID_MASK, + 8, PMIC_ARB_CHAN_IS_IRQ_OWNER_MASK); +} + static int pmic_arb_ppid_to_apid_v5(struct spmi_pmic_arb_bus *bus, u16 ppid) { if (!(bus->ppid_to_apid[ppid] & PMIC_ARB_APID_VALID)) @@ -1345,37 +1384,46 @@ static int pmic_arb_get_core_resources_v7(struct platform_device *pdev, return pmic_arb_get_obsrvr_chnls_v2(pdev); } -/* - * Only v7 supports 2 buses. Each bus will get a different apid count, read - * from different registers. - */ -static int pmic_arb_init_apid_v7(struct spmi_pmic_arb_bus *bus, int index) +static int _pmic_arb_init_apid_v7(struct spmi_pmic_arb_bus *bus, int index, + int max_buses, unsigned long periph_mask) { struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; - int ret; + int i; - if (index == 0) { - bus->base_apid = 0; - bus->apid_count = readl_relaxed(pmic_arb->core + PMIC_ARB_FEATURES) & - PMIC_ARB_FEATURES_PERIPH_MASK; - } else if (index == 1) { - bus->base_apid = readl_relaxed(pmic_arb->core + PMIC_ARB_FEATURES) & - PMIC_ARB_FEATURES_PERIPH_MASK; - bus->apid_count = readl_relaxed(pmic_arb->core + PMIC_ARB_FEATURES1) & - PMIC_ARB_FEATURES_PERIPH_MASK; - } else { - dev_err(&bus->spmic->dev, "Unsupported buses count %d detected\n", - bus->id); + if (index < 0 || index >= max_buses) { + dev_err(&bus->spmic->dev, "Unsupported bus index %d detected\n", + index); return -EINVAL; } - if (bus->base_apid + bus->apid_count > pmic_arb->max_periphs) { - dev_err(&bus->spmic->dev, "Unsupported APID count %d detected\n", + bus->base_apid = 0; + bus->apid_count = 0; + for (i = 0; i <= index; i++) { + bus->base_apid += bus->apid_count; + bus->apid_count = readl_relaxed(pmic_arb->core + + PMIC_ARB_FEATURES + i * 4) & + periph_mask; + } + + if (bus->apid_count == 0) { + dev_err(&bus->spmic->dev, "Bus %d not implemented\n", index); + return -EINVAL; + } else if (bus->base_apid + bus->apid_count > pmic_arb->max_periphs) { + dev_err(&bus->spmic->dev, "Unsupported max APID %d detected\n", bus->base_apid + bus->apid_count); return -EINVAL; } - ret = pmic_arb_init_apid_min_max(bus); + return pmic_arb_init_apid_min_max(bus); +} + +/* + * Arbiter v7 supports 2 buses. Each bus will get a different apid count, read + * from different registers. + */ +static int pmic_arb_init_apid_v7(struct spmi_pmic_arb_bus *bus, int index) +{ + int ret = _pmic_arb_init_apid_v7(bus, index, 2, PMIC_ARB_FEATURES_PERIPH_MASK); if (ret) return ret; @@ -1424,6 +1472,102 @@ static int pmic_arb_offset_v7(struct spmi_pmic_arb_bus *bus, u8 sid, u16 addr, return offset; } +static int pmic_arb_get_core_resources_v8(struct platform_device *pdev, + void __iomem *core) +{ + struct spmi_pmic_arb *pmic_arb = platform_get_drvdata(pdev); + + pmic_arb->apid_map = devm_platform_ioremap_resource_byname(pdev, "chnl_map"); + if (IS_ERR(pmic_arb->apid_map)) + return PTR_ERR(pmic_arb->apid_map); + + pmic_arb->core = core; + + pmic_arb->max_periphs = PMIC_ARB_MAX_PERIPHS_V8; + + return pmic_arb_get_obsrvr_chnls_v2(pdev); +} + +static int pmic_arb_get_bus_resources_v8(struct platform_device *pdev, + struct device_node *node, + struct spmi_pmic_arb_bus *bus) +{ + int index; + + index = of_property_match_string(node, "reg-names", "chnl_owner"); + if (index < 0) { + dev_err(&pdev->dev, "chnl_owner reg region missing\n"); + return -EINVAL; + } + + bus->apid_owner = devm_of_iomap(&pdev->dev, node, index, NULL); + + return PTR_ERR_OR_ZERO(bus->apid_owner); +} + +static int pmic_arb_read_apid_map_v8(struct spmi_pmic_arb_bus *bus) +{ + return _pmic_arb_read_apid_map(bus, bus->pmic_arb->apid_map, + PMIC_ARB_V8_PPID_MASK, 0, + PMIC_ARB_V8_CHAN_IS_IRQ_OWNER_MASK); +} + +/* + * Arbiter v8 supports up to 4 buses. Each bus will get a different apid count, read + * from different registers. + */ +static int pmic_arb_init_apid_v8(struct spmi_pmic_arb_bus *bus, int index) +{ + int ret = _pmic_arb_init_apid_v7(bus, index, 4, + PMIC_ARB_FEATURES_V8_PERIPH_MASK); + if (ret) + return ret; + + ret = pmic_arb_read_apid_map_v8(bus); + if (ret) { + dev_err(&bus->spmic->dev, "could not read APID->PPID mapping table, rc= %d\n", + ret); + return ret; + } + + return 0; +} + +/* + * v8 offset per ee and per apid for observer channels and per apid for + * read/write channels. + */ +static int pmic_arb_offset_v8(struct spmi_pmic_arb_bus *bus, u8 sid, u16 addr, + enum pmic_arb_channel ch_type) +{ + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; + u16 apid; + int rc; + u32 offset = 0; + u16 ppid = (sid << 8) | (addr >> 8); + + rc = pmic_arb->ver_ops->ppid_to_apid(bus, ppid); + if (rc < 0) + return rc; + + apid = rc; + switch (ch_type) { + case PMIC_ARB_CHANNEL_OBS: + offset = 0x40000 * pmic_arb->ee + 0x20 * apid; + break; + case PMIC_ARB_CHANNEL_RW: + if (bus->apid_data[apid].write_ee != pmic_arb->ee) { + dev_err(&bus->spmic->dev, "disallowed SPMI write to sid=%u, addr=0x%04X\n", + sid, addr); + return -EPERM; + } + offset = 0x200 * apid; + break; + } + + return offset; +} + static u32 pmic_arb_fmt_cmd_v1(u8 opc, u8 sid, u16 addr, u8 bc) { return (opc << 27) | ((sid & 0xf) << 20) | (addr << 4) | (bc & 0x7); @@ -1490,6 +1634,14 @@ pmic_arb_acc_enable_v7(struct spmi_pmic_arb_bus *bus, u16 n) return pmic_arb->wr_base + 0x100 + 0x1000 * n; } +static void __iomem * +pmic_arb_acc_enable_v8(struct spmi_pmic_arb_bus *bus, u16 n) +{ + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; + + return pmic_arb->wr_base + 0x100 + 0x200 * n; +} + static void __iomem * pmic_arb_irq_status_v1(struct spmi_pmic_arb_bus *bus, u16 n) { @@ -1516,6 +1668,14 @@ pmic_arb_irq_status_v7(struct spmi_pmic_arb_bus *bus, u16 n) return pmic_arb->wr_base + 0x104 + 0x1000 * n; } +static void __iomem * +pmic_arb_irq_status_v8(struct spmi_pmic_arb_bus *bus, u16 n) +{ + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; + + return pmic_arb->wr_base + 0x104 + 0x200 * n; +} + static void __iomem * pmic_arb_irq_clear_v1(struct spmi_pmic_arb_bus *bus, u16 n) { @@ -1542,6 +1702,14 @@ pmic_arb_irq_clear_v7(struct spmi_pmic_arb_bus *bus, u16 n) return pmic_arb->wr_base + 0x108 + 0x1000 * n; } +static void __iomem * +pmic_arb_irq_clear_v8(struct spmi_pmic_arb_bus *bus, u16 n) +{ + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; + + return pmic_arb->wr_base + 0x108 + 0x200 * n; +} + static u32 pmic_arb_apid_map_offset_v2(u16 n) { return 0x800 + 0x4 * n; @@ -1557,6 +1725,12 @@ static u32 pmic_arb_apid_map_offset_v7(u16 n) return 0x2000 + 0x4 * n; } +static u32 pmic_arb_apid_map_offset_v8(u16 n) +{ + /* For v8, offset is from "chnl_map" base register, not "core". */ + return 0x4 * n; +} + static void __iomem * pmic_arb_apid_owner_v2(struct spmi_pmic_arb_bus *bus, u16 n) { @@ -1564,7 +1738,7 @@ pmic_arb_apid_owner_v2(struct spmi_pmic_arb_bus *bus, u16 n) } /* - * For arbiter version 7, APID ownership table registers have independent + * For arbiter version 7 and 8, APID ownership table registers have independent * numbering space for each SPMI bus instance, so each is indexed starting from * 0. */ @@ -1574,6 +1748,12 @@ pmic_arb_apid_owner_v7(struct spmi_pmic_arb_bus *bus, u16 n) return bus->cnfg + 0x4 * (n - bus->base_apid); } +static void __iomem * +pmic_arb_apid_owner_v8(struct spmi_pmic_arb_bus *bus, u16 n) +{ + return bus->apid_owner + 0x4 * (n - bus->base_apid); +} + static const struct pmic_arb_ver_ops pmic_arb_v1 = { .ver_str = "v1", .get_core_resources = pmic_arb_get_core_resources_v1, @@ -1654,6 +1834,23 @@ static const struct pmic_arb_ver_ops pmic_arb_v7 = { .apid_owner = pmic_arb_apid_owner_v7, }; +static const struct pmic_arb_ver_ops pmic_arb_v8 = { + .ver_str = "v8", + .get_core_resources = pmic_arb_get_core_resources_v8, + .get_bus_resources = pmic_arb_get_bus_resources_v8, + .init_apid = pmic_arb_init_apid_v8, + .ppid_to_apid = pmic_arb_ppid_to_apid_v5, + .non_data_cmd = pmic_arb_non_data_cmd_v2, + .offset = pmic_arb_offset_v8, + .fmt_cmd = pmic_arb_fmt_cmd_v2, + .owner_acc_status = pmic_arb_owner_acc_status_v7, + .acc_enable = pmic_arb_acc_enable_v8, + .irq_status = pmic_arb_irq_status_v8, + .irq_clear = pmic_arb_irq_clear_v8, + .apid_map_offset = pmic_arb_apid_map_offset_v8, + .apid_owner = pmic_arb_apid_owner_v8, +}; + static const struct irq_domain_ops pmic_arb_irq_domain_ops = { .activate = qpnpint_irq_domain_activate, .alloc = qpnpint_irq_domain_alloc, @@ -1731,6 +1928,12 @@ static int spmi_pmic_arb_bus_init(struct platform_device *pdev, bus->spmic = ctrl; bus->id = bus_index; + if (pmic_arb->ver_ops->get_bus_resources) { + ret = pmic_arb->ver_ops->get_bus_resources(pdev, node, bus); + if (ret) + return ret; + } + ret = pmic_arb->ver_ops->init_apid(bus, bus_index); if (ret) return ret; @@ -1825,8 +2028,10 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev) pmic_arb->ver_ops = &pmic_arb_v3; else if (hw_ver < PMIC_ARB_VERSION_V7_MIN) pmic_arb->ver_ops = &pmic_arb_v5; - else + else if (hw_ver < PMIC_ARB_VERSION_V8_MIN) pmic_arb->ver_ops = &pmic_arb_v7; + else + pmic_arb->ver_ops = &pmic_arb_v8; err = pmic_arb->ver_ops->get_core_resources(pdev, core); if (err) @@ -1875,6 +2080,7 @@ static void spmi_pmic_arb_remove(struct platform_device *pdev) static const struct of_device_id spmi_pmic_arb_match_table[] = { { .compatible = "qcom,spmi-pmic-arb", }, { .compatible = "qcom,x1e80100-spmi-pmic-arb", }, + { .compatible = "qcom,glymur-spmi-pmic-arb", }, {}, }; MODULE_DEVICE_TABLE(of, spmi_pmic_arb_match_table); From ec833566da57811d9fa3f11745e7153d9155ad66 Mon Sep 17 00:00:00 2001 From: Louis-Alexis Eyraud Date: Fri, 23 Jan 2026 10:20:38 -0800 Subject: [PATCH 221/297] dt-bindings: spmi: spmi-mtk-pmif: Add compatible for MT8189 SoC Add compatible string for the SPMI block on MT8189 SoC, which is compatible with the one used on MT8195. Signed-off-by: Louis-Alexis Eyraud Acked-by: Conor Dooley Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Stephen Boyd Link: https://patch.msgid.link/20260123182039.224314-11-sboyd@kernel.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/spmi/mtk,spmi-mtk-pmif.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/spmi/mtk,spmi-mtk-pmif.yaml b/Documentation/devicetree/bindings/spmi/mtk,spmi-mtk-pmif.yaml index 7f0be0ac644a..dc61d88008a9 100644 --- a/Documentation/devicetree/bindings/spmi/mtk,spmi-mtk-pmif.yaml +++ b/Documentation/devicetree/bindings/spmi/mtk,spmi-mtk-pmif.yaml @@ -26,6 +26,7 @@ properties: - enum: - mediatek,mt8186-spmi - mediatek,mt8188-spmi + - mediatek,mt8189-spmi - const: mediatek,mt8195-spmi reg: From b7f42b0cfb94d4cc96371ef77c4ecffbc1c02ac2 Mon Sep 17 00:00:00 2001 From: Jason Hall Date: Tue, 20 Jan 2026 06:41:19 -0700 Subject: [PATCH 222/297] rust_binder: refactor context management to use KVVec Replace the linked list management in context.rs with KVVec. This simplifies the ownership model by using standard Arc-based tracking and moves away from manual unsafe list removals. The refactor improves memory safety by leveraging Rust's contiguous collection types while maintaining proper error propagation for allocation failures during process registration. Suggested-by: Alice Ryhl Link: https://github.com/rust-for-linux/linux/issues/1215 Signed-off-by: Jason Hall Link: https://patch.msgid.link/20260120134119.98048-1-jason.kei.hall@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/context.rs | 84 +++++++++++++------------------ drivers/android/binder/process.rs | 5 +- 2 files changed, 36 insertions(+), 53 deletions(-) diff --git a/drivers/android/binder/context.rs b/drivers/android/binder/context.rs index 3d135ec03ca7..c7b75efef217 100644 --- a/drivers/android/binder/context.rs +++ b/drivers/android/binder/context.rs @@ -3,8 +3,8 @@ // Copyright (C) 2025 Google LLC. use kernel::{ - error::Error, - list::{List, ListArc, ListLinks}, + alloc::kvec::KVVec, + error::code::*, prelude::*, security, str::{CStr, CString}, @@ -17,22 +17,19 @@ use crate::{error::BinderError, node::NodeRef, process::Process}; kernel::sync::global_lock! { // SAFETY: We call `init` in the module initializer, so it's initialized before first use. pub(crate) unsafe(uninit) static CONTEXTS: Mutex = ContextList { - list: List::new(), + contexts: KVVec::new(), }; } pub(crate) struct ContextList { - list: List, + contexts: KVVec>, } -pub(crate) fn get_all_contexts() -> Result>> { +pub(crate) fn get_all_contexts() -> Result>> { let lock = CONTEXTS.lock(); - - let count = lock.list.iter().count(); - - let mut ctxs = KVec::with_capacity(count, GFP_KERNEL)?; - for ctx in &lock.list { - ctxs.push(Arc::from(ctx), GFP_KERNEL)?; + let mut ctxs = KVVec::with_capacity(lock.contexts.len(), GFP_KERNEL)?; + for ctx in lock.contexts.iter() { + ctxs.push(ctx.clone(), GFP_KERNEL)?; } Ok(ctxs) } @@ -42,7 +39,7 @@ pub(crate) fn get_all_contexts() -> Result>> { struct Manager { node: Option, uid: Option, - all_procs: List, + all_procs: KVVec>, } /// There is one context per binder file (/dev/binder, /dev/hwbinder, etc) @@ -51,28 +48,16 @@ pub(crate) struct Context { #[pin] manager: Mutex, pub(crate) name: CString, - #[pin] - links: ListLinks, -} - -kernel::list::impl_list_arc_safe! { - impl ListArcSafe<0> for Context { untracked; } -} -kernel::list::impl_list_item! { - impl ListItem<0> for Context { - using ListLinks { self.links }; - } } impl Context { pub(crate) fn new(name: &CStr) -> Result> { let name = CString::try_from(name)?; - let list_ctx = ListArc::pin_init::( + let ctx = Arc::pin_init( try_pin_init!(Context { name, - links <- ListLinks::new(), manager <- kernel::new_mutex!(Manager { - all_procs: List::new(), + all_procs: KVVec::new(), node: None, uid: None, }, "Context::manager"), @@ -80,8 +65,7 @@ impl Context { GFP_KERNEL, )?; - let ctx = list_ctx.clone_arc(); - CONTEXTS.lock().list.push_back(list_ctx); + CONTEXTS.lock().contexts.push(ctx.clone(), GFP_KERNEL)?; Ok(ctx) } @@ -89,27 +73,27 @@ impl Context { /// Called when the file for this context is unlinked. /// /// No-op if called twice. - pub(crate) fn deregister(&self) { - // SAFETY: We never add the context to any other linked list than this one, so it is either - // in this list, or not in any list. - unsafe { CONTEXTS.lock().list.remove(self) }; + pub(crate) fn deregister(self: &Arc) { + // Safe removal using retain + CONTEXTS.lock().contexts.retain(|c| !Arc::ptr_eq(c, self)); } - pub(crate) fn register_process(self: &Arc, proc: ListArc) { + pub(crate) fn register_process(self: &Arc, proc: Arc) -> Result { if !Arc::ptr_eq(self, &proc.ctx) { pr_err!("Context::register_process called on the wrong context."); - return; + return Err(EINVAL); } - self.manager.lock().all_procs.push_back(proc); + self.manager.lock().all_procs.push(proc, GFP_KERNEL)?; + Ok(()) } - pub(crate) fn deregister_process(self: &Arc, proc: &Process) { + pub(crate) fn deregister_process(self: &Arc, proc: &Arc) { if !Arc::ptr_eq(self, &proc.ctx) { pr_err!("Context::deregister_process called on the wrong context."); return; } - // SAFETY: We just checked that this is the right list. - unsafe { self.manager.lock().all_procs.remove(proc) }; + let mut manager = self.manager.lock(); + manager.all_procs.retain(|p| !Arc::ptr_eq(p, proc)); } pub(crate) fn set_manager_node(&self, node_ref: NodeRef) -> Result { @@ -158,23 +142,23 @@ impl Context { } } - pub(crate) fn get_all_procs(&self) -> Result>> { + pub(crate) fn get_all_procs(&self) -> Result>> { let lock = self.manager.lock(); - let count = lock.all_procs.iter().count(); - - let mut procs = KVec::with_capacity(count, GFP_KERNEL)?; - for proc in &lock.all_procs { - procs.push(Arc::from(proc), GFP_KERNEL)?; + let mut procs = KVVec::with_capacity(lock.all_procs.len(), GFP_KERNEL)?; + for proc in lock.all_procs.iter() { + procs.push(Arc::clone(proc), GFP_KERNEL)?; } Ok(procs) } - pub(crate) fn get_procs_with_pid(&self, pid: i32) -> Result>> { - let orig = self.get_all_procs()?; - let mut backing = KVec::with_capacity(orig.len(), GFP_KERNEL)?; - for proc in orig.into_iter().filter(|proc| proc.task.pid() == pid) { - backing.push(proc, GFP_KERNEL)?; + pub(crate) fn get_procs_with_pid(&self, pid: i32) -> Result>> { + let lock = self.manager.lock(); + let mut matching_procs = KVVec::new(); + for proc in lock.all_procs.iter() { + if proc.task.pid() == pid { + matching_procs.push(Arc::clone(proc), GFP_KERNEL)?; + } } - Ok(backing) + Ok(matching_procs) } } diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs index 62b8279b711f..ba98ef9f3545 100644 --- a/drivers/android/binder/process.rs +++ b/drivers/android/binder/process.rs @@ -503,7 +503,7 @@ impl workqueue::WorkItem for Process { impl Process { fn new(ctx: Arc, cred: ARef) -> Result> { let current = kernel::current!(); - let list_process = ListArc::pin_init::( + let process = Arc::pin_init::( try_pin_init!(Process { ctx, cred, @@ -519,8 +519,7 @@ impl Process { GFP_KERNEL, )?; - let process = list_process.clone_arc(); - process.ctx.register_process(list_process); + process.ctx.register_process(process.clone())?; Ok(process) } From f397bc0781553d01b4cdba506c09334a31cb0ec5 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 16 Jan 2026 17:08:43 +0000 Subject: [PATCH 223/297] nvmem: Drop OF node reference on nvmem_add_one_cell() failure If nvmem_add_one_cell() failed, the ownership of "child" (or "info.np"), thus its OF reference, is not passed further and function should clean up by putting the reference it got via earlier of_node_get(). Note that this is independent of references obtained via for_each_child_of_node() loop. Fixes: 50014d659617 ("nvmem: core: use nvmem_add_one_cell() in nvmem_add_cells_from_of()") Cc: stable@vger.kernel.org Signed-off-by: Krzysztof Kozlowski Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260116170846.733558-2-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 387c88c55259..ff68fd5ad3d6 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -831,6 +831,7 @@ static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_nod kfree(info.name); if (ret) { of_node_put(child); + of_node_put(info.np); return ret; } } From e75c79f24927dd46a97f511e1dcb2bedfe29155e Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 16 Jan 2026 17:08:44 +0000 Subject: [PATCH 224/297] nvmem: Simplify with scoped for each OF child loop Use scoped for-each loop when iterating over device nodes to make code a bit simpler. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260116170846.733558-3-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/core.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index ff68fd5ad3d6..c6180cf1dd91 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -789,11 +789,10 @@ static int nvmem_validate_keepouts(struct nvmem_device *nvmem) static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np) { struct device *dev = &nvmem->dev; - struct device_node *child; const __be32 *addr; int len, ret; - for_each_child_of_node(np, child) { + for_each_child_of_node_scoped(np, child) { struct nvmem_cell_info info = {0}; addr = of_get_property(child, "reg", &len); @@ -801,7 +800,6 @@ static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_nod continue; if (len < 2 * sizeof(u32)) { dev_err(dev, "nvmem: invalid reg on %pOF\n", child); - of_node_put(child); return -EINVAL; } @@ -817,7 +815,6 @@ static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_nod info.nbits < 1 || info.bit_offset + info.nbits > BITS_PER_BYTE * info.bytes) { dev_err(dev, "nvmem: invalid bits on %pOF\n", child); - of_node_put(child); return -EINVAL; } } @@ -830,7 +827,6 @@ static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_nod ret = nvmem_add_one_cell(nvmem, &info); kfree(info.name); if (ret) { - of_node_put(child); of_node_put(info.np); return ret; } From 4796eaafd6a170db012395a40385d2baf4f4d118 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Fri, 16 Jan 2026 17:08:45 +0000 Subject: [PATCH 225/297] nvmem: an8855: drop an unused Kconfig symbol MFD_AIROHA_AN8855 is referenced here but never defined, so drop it from the Kconfig file. Fixes: e2258cfd9b98 ("nvmem: an8855: Add support for Airoha AN8855 Switch EFUSE") Signed-off-by: Randy Dunlap Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260116170846.733558-4-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index bf47a982cf62..74ddbd0f79b0 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -30,7 +30,7 @@ source "drivers/nvmem/layouts/Kconfig" config NVMEM_AN8855_EFUSE tristate "Airoha AN8855 eFuse support" - depends on MFD_AIROHA_AN8855 || COMPILE_TEST + depends on COMPILE_TEST help Say y here to enable support for reading eFuses on Airoha AN8855 Switch. These are e.g. used to store factory programmed From e94865ca2340e9eaec08716dfad645e9f719eedd Mon Sep 17 00:00:00 2001 From: Komal Bajaj Date: Fri, 16 Jan 2026 17:08:46 +0000 Subject: [PATCH 226/297] dt-bindings: nvmem: qfprom: Add sm8750 compatible Document compatible string for the QFPROM on SM8750 platform. Signed-off-by: Komal Bajaj Reviewed-by: Akhil P Oommen Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260116170846.733558-5-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml b/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml index 7d1612acca48..839513d4b499 100644 --- a/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml +++ b/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml @@ -55,6 +55,7 @@ properties: - qcom,sm8450-qfprom - qcom,sm8550-qfprom - qcom,sm8650-qfprom + - qcom,sm8750-qfprom - qcom,x1e80100-qfprom - const: qcom,qfprom From 2c198c272f9c9213b0fdf6b4a879f445c574f416 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 16 Jan 2026 17:29:50 +0100 Subject: [PATCH 227/297] most: core: fix leak on early registration failure A recent commit fixed a resource leak on early registration failures but for some reason left out the first error path which still leaks the resources associated with the interface. Fix up also the first error path so that the interface is always released on errors. Fixes: 1f4c9d8a1021 ("most: core: fix resource leak in most_register_interface error paths") Fixes: 723de0f9171e ("staging: most: remove device from interface structure") Cc: Christian Gromm Cc: Navaneeth K Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260116162950.21578-1-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/most/core.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/most/core.c b/drivers/most/core.c index 6277e6702ca8..40d63e38fef5 100644 --- a/drivers/most/core.c +++ b/drivers/most/core.c @@ -1282,12 +1282,17 @@ int most_register_interface(struct most_interface *iface) int id; struct most_channel *c; - if (!iface || !iface->enqueue || !iface->configure || - !iface->poison_channel || (iface->num_channels > MAX_CHANNELS)) + if (!iface) return -EINVAL; device_initialize(iface->dev); + if (!iface->enqueue || !iface->configure || !iface->poison_channel || + (iface->num_channels > MAX_CHANNELS)) { + put_device(iface->dev); + return -EINVAL; + } + id = ida_alloc(&mdev_id, GFP_KERNEL); if (id < 0) { dev_err(iface->dev, "Failed to allocate device ID\n"); From be0801e530f76890ffa7cf35f58c327b55f972dd Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 16 Jan 2026 17:09:42 +0000 Subject: [PATCH 228/297] slimbus: qcom-ngd: Simplify with scoped for each OF child loop Use scoped for-each loop when iterating over device nodes to make code a bit simpler. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Konrad Dybcio Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260116170942.733674-1-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/qcom-ngd-ctrl.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/slimbus/qcom-ngd-ctrl.c b/drivers/slimbus/qcom-ngd-ctrl.c index ba3d80d12605..7338b38697d0 100644 --- a/drivers/slimbus/qcom-ngd-ctrl.c +++ b/drivers/slimbus/qcom-ngd-ctrl.c @@ -1514,26 +1514,22 @@ static int of_qcom_slim_ngd_register(struct device *parent, const struct ngd_reg_offset_data *data; struct qcom_slim_ngd *ngd; const struct of_device_id *match; - struct device_node *node; u32 id; int ret; match = of_match_node(qcom_slim_ngd_dt_match, parent->of_node); data = match->data; - for_each_available_child_of_node(parent->of_node, node) { + for_each_available_child_of_node_scoped(parent->of_node, node) { if (of_property_read_u32(node, "reg", &id)) continue; ngd = kzalloc(sizeof(*ngd), GFP_KERNEL); - if (!ngd) { - of_node_put(node); + if (!ngd) return -ENOMEM; - } ngd->pdev = platform_device_alloc(QCOM_SLIM_NGD_DRV_NAME, id); if (!ngd->pdev) { kfree(ngd); - of_node_put(node); return -ENOMEM; } ngd->id = id; @@ -1546,7 +1542,6 @@ static int of_qcom_slim_ngd_register(struct device *parent, if (ret) { platform_device_put(ngd->pdev); kfree(ngd); - of_node_put(node); return ret; } ngd->pdev->dev.of_node = node; @@ -1556,7 +1551,6 @@ static int of_qcom_slim_ngd_register(struct device *parent, if (ret) { platform_device_put(ngd->pdev); kfree(ngd); - of_node_put(node); return ret; } ngd->base = ctrl->base + ngd->id * data->offset + From bc2e4bc952e26dd93b978588219044bd8b24237b Mon Sep 17 00:00:00 2001 From: Jose Javier Rodriguez Barbarin Date: Fri, 16 Jan 2026 12:21:41 +0100 Subject: [PATCH 229/297] mcb: fix incorrect sanity check __mcb_register_driver() makes some sanity checks over mcb_driver to check if .probe and .remove callbacks are set. However, since commit 3bd13ae04ccc ("gpio: menz127: simplify error path and remove remove()") removed the .remove callback from menz127-gpio.c, not all mcb device drivers implement .remove callback. Remove .remove check to ensure all mcb device drivers can be loaded. Signed-off-by: Jose Javier Rodriguez Barbarin Fixes: 3bd13ae04ccc ("gpio: menz127: simplify error path and remove remove()") [ jth: added statement about menz127-gpio.c ] Signed-off-by: Johannes Thumshirn Link: https://patch.msgid.link/16fb55bd59d9c1d2ce2443f41d4dec2048f9a8ec.1768562302.git.jth@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/mcb/mcb-core.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/mcb/mcb-core.c b/drivers/mcb/mcb-core.c index c1367223e71a..3d487d75c483 100644 --- a/drivers/mcb/mcb-core.c +++ b/drivers/mcb/mcb-core.c @@ -85,7 +85,8 @@ static void mcb_remove(struct device *dev) struct mcb_device *mdev = to_mcb_device(dev); struct module *carrier_mod; - mdrv->remove(mdev); + if (mdrv->remove) + mdrv->remove(mdev); carrier_mod = mdev->dev.parent->driver->owner; module_put(carrier_mod); @@ -176,13 +177,13 @@ static const struct device_type mcb_carrier_device_type = { * @owner: The @mcb_driver's module * @mod_name: The name of the @mcb_driver's module * - * Register a @mcb_driver at the system. Perform some sanity checks, if - * the .probe and .remove methods are provided by the driver. + * Register a @mcb_driver at the system. Perform a sanity check, if + * .probe method is provided by the driver. */ int __mcb_register_driver(struct mcb_driver *drv, struct module *owner, const char *mod_name) { - if (!drv->probe || !drv->remove) + if (!drv->probe) return -EINVAL; drv->driver.owner = owner; From 89b0bb232a4c71eeb18f75bfd7aa1ae850025c87 Mon Sep 17 00:00:00 2001 From: Pawel Chmielewski Date: Fri, 16 Jan 2026 18:52:49 +0100 Subject: [PATCH 230/297] intel_th: pci: Use PCI_DEVICE_DATA() for device entries Switch PCI_DEVICE() macro to PCI_DEVICE_DATA() to reduce the number of lines. Add a new header file for the defined names of the devices. Sort the entries in ascending order. Signed-off-by: Pawel Chmielewski Reviewed-by: Alan Borzeszkowski Reviewed-by: Andy Shevchenko Signed-off-by: Alexander Shishkin Link: https://patch.msgid.link/20260116175250.821002-2-alexander.shishkin@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/intel_th/pci.c | 265 +++++---------------------- drivers/hwtracing/intel_th/pci_ids.h | 56 ++++++ 2 files changed, 102 insertions(+), 219 deletions(-) create mode 100644 drivers/hwtracing/intel_th/pci_ids.h diff --git a/drivers/hwtracing/intel_th/pci.c b/drivers/hwtracing/intel_th/pci.c index e3def163d5cf..de069c7aea7c 100644 --- a/drivers/hwtracing/intel_th/pci.c +++ b/drivers/hwtracing/intel_th/pci.c @@ -14,6 +14,7 @@ #include #include "intel_th.h" +#include "pci_ids.h" #define DRIVER_NAME "intel_th_pci" @@ -141,225 +142,51 @@ static const struct intel_th_drvdata intel_th_2x = { }; static const struct pci_device_id intel_th_pci_id_table[] = { - { - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x9d26), - .driver_data = (kernel_ulong_t)0, - }, - { - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa126), - .driver_data = (kernel_ulong_t)0, - }, - { - /* Apollo Lake */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x5a8e), - .driver_data = (kernel_ulong_t)0, - }, - { - /* Broxton */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0a80), - .driver_data = (kernel_ulong_t)0, - }, - { - /* Broxton B-step */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x1a8e), - .driver_data = (kernel_ulong_t)0, - }, - { - /* Kaby Lake PCH-H */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa2a6), - .driver_data = (kernel_ulong_t)&intel_th_1x_multi_is_broken, - }, - { - /* Denverton */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x19e1), - .driver_data = (kernel_ulong_t)0, - }, - { - /* Lewisburg PCH */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa1a6), - .driver_data = (kernel_ulong_t)0, - }, - { - /* Lewisburg PCH */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa226), - .driver_data = (kernel_ulong_t)0, - }, - { - /* Gemini Lake */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x318e), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Cannon Lake H */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa326), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Cannon Lake LP */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x9da6), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Cedar Fork PCH */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x18e1), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Ice Lake PCH */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x34a6), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Comet Lake */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x02a6), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Comet Lake PCH */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x06a6), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Comet Lake PCH-V */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa3a6), - .driver_data = (kernel_ulong_t)&intel_th_1x_multi_is_broken, - }, - { - /* Ice Lake NNPI */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x45c5), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Ice Lake CPU */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x8a29), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Tiger Lake CPU */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x9a33), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Tiger Lake PCH */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa0a6), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Tiger Lake PCH-H */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x43a6), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Jasper Lake PCH */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x4da6), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Jasper Lake CPU */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x4e29), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Elkhart Lake CPU */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x4529), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Elkhart Lake */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x4b26), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Emmitsburg PCH */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x1bcc), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Alder Lake */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x7aa6), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Alder Lake-P */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x51a6), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Alder Lake-M */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x54a6), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Meteor Lake-P */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x7e24), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Meteor Lake-S */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x7f26), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Meteor Lake-S CPU */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xae24), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Raptor Lake-S */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x7a26), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Raptor Lake-S CPU */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa76f), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Granite Rapids */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0963), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Granite Rapids SOC */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x3256), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Sapphire Rapids SOC */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x3456), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Lunar Lake */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa824), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Arrow Lake */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x7724), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Panther Lake-H */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xe324), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Panther Lake-P/U */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xe424), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Alder Lake CPU */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x466f), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Rocket Lake CPU */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x4c19), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { 0 }, + { PCI_DEVICE_DATA(INTEL, NPK_CML, &intel_th_2x) }, /* Comet Lake */ + { PCI_DEVICE_DATA(INTEL, NPK_CML_PCH, &intel_th_2x) }, /* Comet Lake PCH */ + { PCI_DEVICE_DATA(INTEL, NPK_GNR, &intel_th_2x) }, /* Granite Rapids */ + { PCI_DEVICE_DATA(INTEL, NPK_BXT, NULL) }, /* Broxton */ + { PCI_DEVICE_DATA(INTEL, NPK_CDF, &intel_th_2x) }, /* Cedar Fork PCH */ + { PCI_DEVICE_DATA(INTEL, NPK_DNV, NULL) }, /* Denverton */ + { PCI_DEVICE_DATA(INTEL, NPK_BXT_B, NULL) }, /* Broxton B-step */ + { PCI_DEVICE_DATA(INTEL, NPK_EBG, &intel_th_2x) }, /* Emmitsburg PCH */ + { PCI_DEVICE_DATA(INTEL, NPK_GLK, &intel_th_2x) }, /* Gemini Lake */ + { PCI_DEVICE_DATA(INTEL, NPK_GNR_SOC, &intel_th_2x) }, /* Granite Rapids SOC */ + { PCI_DEVICE_DATA(INTEL, NPK_SPR, &intel_th_2x) }, /* Sapphire Rapids SOC */ + { PCI_DEVICE_DATA(INTEL, NPK_ICL_PCH, &intel_th_2x) }, /* Ice Lake PCH */ + { PCI_DEVICE_DATA(INTEL, NPK_TGL_PCH_H, &intel_th_2x) }, /* Tiger Lake PCH-H */ + { PCI_DEVICE_DATA(INTEL, NPK_EHL_CPU, &intel_th_2x) }, /* Elkhart Lake CPU */ + { PCI_DEVICE_DATA(INTEL, NPK_ICL_NNPI, &intel_th_2x) }, /* Ice Lake NNPI */ + { PCI_DEVICE_DATA(INTEL, NPK_ADL_CPU, &intel_th_2x) }, /* Alder Lake CPU */ + { PCI_DEVICE_DATA(INTEL, NPK_EHL, &intel_th_2x) }, /* Elkhart Lake */ + { PCI_DEVICE_DATA(INTEL, NPK_RKL, &intel_th_2x) }, /* Rocket Lake CPU */ + { PCI_DEVICE_DATA(INTEL, NPK_JSL_PCH, &intel_th_2x) }, /* Jasper Lake PCH */ + { PCI_DEVICE_DATA(INTEL, NPK_JSL_CPU, &intel_th_2x) }, /* Jasper Lake CPU */ + { PCI_DEVICE_DATA(INTEL, NPK_ADL_P, &intel_th_2x) }, /* Alder Lake-P */ + { PCI_DEVICE_DATA(INTEL, NPK_ADL_M, &intel_th_2x) }, /* Alder Lake-M */ + { PCI_DEVICE_DATA(INTEL, NPK_APL, NULL) }, /* Apollo Lake */ + { PCI_DEVICE_DATA(INTEL, NPK_ARL, &intel_th_2x) }, /* Arrow Lake */ + { PCI_DEVICE_DATA(INTEL, NPK_RPL_S, &intel_th_2x) }, /* Raptor Lake-S */ + { PCI_DEVICE_DATA(INTEL, NPK_ADL, &intel_th_2x) }, /* Alder Lake */ + { PCI_DEVICE_DATA(INTEL, NPK_MTL_P, &intel_th_2x) }, /* Meteor Lake-P */ + { PCI_DEVICE_DATA(INTEL, NPK_MTL_S, &intel_th_2x) }, /* Meteor Lake-S */ + { PCI_DEVICE_DATA(INTEL, NPK_ICL_CPU, &intel_th_2x) }, /* Ice Lake CPU */ + { PCI_DEVICE_DATA(INTEL, NPK_TGL_CPU, &intel_th_2x) }, /* Tiger Lake CPU */ + { PCI_DEVICE_DATA(INTEL, NPK_0, NULL) }, + { PCI_DEVICE_DATA(INTEL, NPK_CNL_LP, &intel_th_2x) }, /* Cannon Lake LP */ + { PCI_DEVICE_DATA(INTEL, NPK_TGL_PCH, &intel_th_2x) }, /* Tiger Lake PCH */ + { PCI_DEVICE_DATA(INTEL, NPK_1, NULL) }, + { PCI_DEVICE_DATA(INTEL, NPK_LBG_PCH, NULL) }, /* Lewisburg PCH */ + { PCI_DEVICE_DATA(INTEL, NPK_LBG_PCH_2, NULL) }, /* Lewisburg PCH */ + { PCI_DEVICE_DATA(INTEL, NPK_KBL_PCH, &intel_th_1x_multi_is_broken) }, /* Kaby Lake PCH-H */ + { PCI_DEVICE_DATA(INTEL, NPK_CNL_H, &intel_th_2x) }, /* Cannon Lake H */ + { PCI_DEVICE_DATA(INTEL, NPK_CML_PCH_V, &intel_th_1x_multi_is_broken) }, /* Comet Lake PCH-V */ + { PCI_DEVICE_DATA(INTEL, NPK_RPL_S_CPU, &intel_th_2x) }, /* Raptor Lake-S CPU */ + { PCI_DEVICE_DATA(INTEL, NPK_LNL, &intel_th_2x) }, /* Lunar Lake */ + { PCI_DEVICE_DATA(INTEL, NPK_MTL_S_CPU, &intel_th_2x) }, /* Meteor Lake-S CPU */ + { PCI_DEVICE_DATA(INTEL, NPK_PTL_H, &intel_th_2x) }, /* Panther Lake-H */ + { PCI_DEVICE_DATA(INTEL, NPK_PTL_PU, &intel_th_2x) }, /* Panther Lake-P/U */ + { } }; MODULE_DEVICE_TABLE(pci, intel_th_pci_id_table); diff --git a/drivers/hwtracing/intel_th/pci_ids.h b/drivers/hwtracing/intel_th/pci_ids.h new file mode 100644 index 000000000000..7dad5d7621f5 --- /dev/null +++ b/drivers/hwtracing/intel_th/pci_ids.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Intel(R) Trace Hub driver debugging + * + * Copyright (C) 2025 Intel Corporation. + */ + +#ifndef __INTEL_TH_PCI_IDS_H__ +#define __INTEL_TH_PCI_IDS_H__ + +#define PCI_DEVICE_ID_INTEL_NPK_CML 0x02a6 +#define PCI_DEVICE_ID_INTEL_NPK_CML_PCH 0x06a6 +#define PCI_DEVICE_ID_INTEL_NPK_GNR 0x0963 +#define PCI_DEVICE_ID_INTEL_NPK_BXT 0x0a80 +#define PCI_DEVICE_ID_INTEL_NPK_CDF 0x18e1 +#define PCI_DEVICE_ID_INTEL_NPK_DNV 0x19e1 +#define PCI_DEVICE_ID_INTEL_NPK_BXT_B 0x1a8e +#define PCI_DEVICE_ID_INTEL_NPK_EBG 0x1bcc +#define PCI_DEVICE_ID_INTEL_NPK_GLK 0x318e +#define PCI_DEVICE_ID_INTEL_NPK_GNR_SOC 0x3256 +#define PCI_DEVICE_ID_INTEL_NPK_SPR 0x3456 +#define PCI_DEVICE_ID_INTEL_NPK_ICL_PCH 0x34a6 +#define PCI_DEVICE_ID_INTEL_NPK_TGL_PCH_H 0x43a6 +#define PCI_DEVICE_ID_INTEL_NPK_EHL_CPU 0x4529 +#define PCI_DEVICE_ID_INTEL_NPK_ICL_NNPI 0x45c5 +#define PCI_DEVICE_ID_INTEL_NPK_ADL_CPU 0x466f +#define PCI_DEVICE_ID_INTEL_NPK_EHL 0x4b26 +#define PCI_DEVICE_ID_INTEL_NPK_RKL 0x4c19 +#define PCI_DEVICE_ID_INTEL_NPK_JSL_PCH 0x4da6 +#define PCI_DEVICE_ID_INTEL_NPK_JSL_CPU 0x4e29 +#define PCI_DEVICE_ID_INTEL_NPK_ADL_P 0x51a6 +#define PCI_DEVICE_ID_INTEL_NPK_ADL_M 0x54a6 +#define PCI_DEVICE_ID_INTEL_NPK_APL 0x5a8e +#define PCI_DEVICE_ID_INTEL_NPK_ARL 0x7724 +#define PCI_DEVICE_ID_INTEL_NPK_RPL_S 0x7a26 +#define PCI_DEVICE_ID_INTEL_NPK_ADL 0x7aa6 +#define PCI_DEVICE_ID_INTEL_NPK_MTL_P 0x7e24 +#define PCI_DEVICE_ID_INTEL_NPK_MTL_S 0x7f26 +#define PCI_DEVICE_ID_INTEL_NPK_ICL_CPU 0x8a29 +#define PCI_DEVICE_ID_INTEL_NPK_TGL_CPU 0x9a33 +#define PCI_DEVICE_ID_INTEL_NPK_0 0x9d26 +#define PCI_DEVICE_ID_INTEL_NPK_CNL_LP 0x9da6 +#define PCI_DEVICE_ID_INTEL_NPK_TGL_PCH 0xa0a6 +#define PCI_DEVICE_ID_INTEL_NPK_1 0xa126 +#define PCI_DEVICE_ID_INTEL_NPK_LBG_PCH 0xa1a6 +#define PCI_DEVICE_ID_INTEL_NPK_LBG_PCH_2 0xa226 +#define PCI_DEVICE_ID_INTEL_NPK_KBL_PCH 0xa2a6 +#define PCI_DEVICE_ID_INTEL_NPK_CNL_H 0xa326 +#define PCI_DEVICE_ID_INTEL_NPK_CML_PCH_V 0xa3a6 +#define PCI_DEVICE_ID_INTEL_NPK_RPL_S_CPU 0xa76f +#define PCI_DEVICE_ID_INTEL_NPK_LNL 0xa824 +#define PCI_DEVICE_ID_INTEL_NPK_MTL_S_CPU 0xae24 +#define PCI_DEVICE_ID_INTEL_NPK_PTL_H 0xe324 +#define PCI_DEVICE_ID_INTEL_NPK_PTL_PU 0xe424 + +#endif /* __INTEL_TH_PCI_IDS_H__ */ From 1814159b0d0a21bb05ab7b62d2defadfad84ef68 Mon Sep 17 00:00:00 2001 From: Pawel Chmielewski Date: Fri, 16 Jan 2026 18:52:50 +0100 Subject: [PATCH 231/297] intel_th: pci: Add Nova Lake support Add support for the Trace Hub in Nova Lake-P/H/S and PCH. Signed-off-by: Pawel Chmielewski Reviewed-by: Alan Borzeszkowski Reviewed-by: Andy Shevchenko Signed-off-by: Alexander Shishkin Link: https://patch.msgid.link/20260116175250.821002-3-alexander.shishkin@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/intel_th/pci.c | 4 ++++ drivers/hwtracing/intel_th/pci_ids.h | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/drivers/hwtracing/intel_th/pci.c b/drivers/hwtracing/intel_th/pci.c index de069c7aea7c..6cd5ac3c6430 100644 --- a/drivers/hwtracing/intel_th/pci.c +++ b/drivers/hwtracing/intel_th/pci.c @@ -165,6 +165,7 @@ static const struct pci_device_id intel_th_pci_id_table[] = { { PCI_DEVICE_DATA(INTEL, NPK_ADL_P, &intel_th_2x) }, /* Alder Lake-P */ { PCI_DEVICE_DATA(INTEL, NPK_ADL_M, &intel_th_2x) }, /* Alder Lake-M */ { PCI_DEVICE_DATA(INTEL, NPK_APL, NULL) }, /* Apollo Lake */ + { PCI_DEVICE_DATA(INTEL, NPK_NVL_PCH, &intel_th_2x) }, /* Nova Lake-PCH */ { PCI_DEVICE_DATA(INTEL, NPK_ARL, &intel_th_2x) }, /* Arrow Lake */ { PCI_DEVICE_DATA(INTEL, NPK_RPL_S, &intel_th_2x) }, /* Raptor Lake-S */ { PCI_DEVICE_DATA(INTEL, NPK_ADL, &intel_th_2x) }, /* Alder Lake */ @@ -184,6 +185,9 @@ static const struct pci_device_id intel_th_pci_id_table[] = { { PCI_DEVICE_DATA(INTEL, NPK_RPL_S_CPU, &intel_th_2x) }, /* Raptor Lake-S CPU */ { PCI_DEVICE_DATA(INTEL, NPK_LNL, &intel_th_2x) }, /* Lunar Lake */ { PCI_DEVICE_DATA(INTEL, NPK_MTL_S_CPU, &intel_th_2x) }, /* Meteor Lake-S CPU */ + { PCI_DEVICE_DATA(INTEL, NPK_NVL_P, &intel_th_2x) }, /* Nova Lake-P */ + { PCI_DEVICE_DATA(INTEL, NPK_NVL_H, &intel_th_2x) }, /* Nova Lake-H */ + { PCI_DEVICE_DATA(INTEL, NPK_NVL_S, &intel_th_2x) }, /* Nova Lake-S */ { PCI_DEVICE_DATA(INTEL, NPK_PTL_H, &intel_th_2x) }, /* Panther Lake-H */ { PCI_DEVICE_DATA(INTEL, NPK_PTL_PU, &intel_th_2x) }, /* Panther Lake-P/U */ { } diff --git a/drivers/hwtracing/intel_th/pci_ids.h b/drivers/hwtracing/intel_th/pci_ids.h index 7dad5d7621f5..4a0c53beac22 100644 --- a/drivers/hwtracing/intel_th/pci_ids.h +++ b/drivers/hwtracing/intel_th/pci_ids.h @@ -31,6 +31,7 @@ #define PCI_DEVICE_ID_INTEL_NPK_ADL_P 0x51a6 #define PCI_DEVICE_ID_INTEL_NPK_ADL_M 0x54a6 #define PCI_DEVICE_ID_INTEL_NPK_APL 0x5a8e +#define PCI_DEVICE_ID_INTEL_NPK_NVL_PCH 0x6e26 #define PCI_DEVICE_ID_INTEL_NPK_ARL 0x7724 #define PCI_DEVICE_ID_INTEL_NPK_RPL_S 0x7a26 #define PCI_DEVICE_ID_INTEL_NPK_ADL 0x7aa6 @@ -50,6 +51,9 @@ #define PCI_DEVICE_ID_INTEL_NPK_RPL_S_CPU 0xa76f #define PCI_DEVICE_ID_INTEL_NPK_LNL 0xa824 #define PCI_DEVICE_ID_INTEL_NPK_MTL_S_CPU 0xae24 +#define PCI_DEVICE_ID_INTEL_NPK_NVL_P 0xd224 +#define PCI_DEVICE_ID_INTEL_NPK_NVL_H 0xd324 +#define PCI_DEVICE_ID_INTEL_NPK_NVL_S 0xd424 #define PCI_DEVICE_ID_INTEL_NPK_PTL_H 0xe324 #define PCI_DEVICE_ID_INTEL_NPK_PTL_PU 0xe424 From 0bb23328ab6cab866eb096ae3714a295c6f67c0f Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Thu, 22 Jan 2026 19:55:47 -0800 Subject: [PATCH 232/297] pps: generators: remove broken pps_gen_parport driver This driver was introduced in January 2011 and has been marked BROKEN for almost its entire existence, since commit 95b90afec301 ("pps: make pps_gen_parport depend on BROKEN") in March 2011. It is unlikely anyone will fix the driver at this point. Signed-off-by: Ethan Nelson-Moore Acked-by: Rodolfo Giometti Link: https://patch.msgid.link/20260123035607.22340-1-enelsonmoore@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/pps/generators/Kconfig | 8 - drivers/pps/generators/Makefile | 1 - drivers/pps/generators/pps_gen_parport.c | 238 ----------------------- 3 files changed, 247 deletions(-) delete mode 100644 drivers/pps/generators/pps_gen_parport.c diff --git a/drivers/pps/generators/Kconfig b/drivers/pps/generators/Kconfig index b3f340ed3163..4ef02b3f2576 100644 --- a/drivers/pps/generators/Kconfig +++ b/drivers/pps/generators/Kconfig @@ -23,14 +23,6 @@ config PPS_GENERATOR_DUMMY This driver can also be built as a module. If so, the module will be called pps_gen-dummy. -config PPS_GENERATOR_PARPORT - tristate "Parallel port PPS signal generator" - depends on PARPORT && BROKEN - help - If you say yes here you get support for a PPS signal generator which - utilizes STROBE pin of a parallel port to send PPS signals. It uses - parport abstraction layer and hrtimers to precisely control the signal. - config PPS_GENERATOR_TIO tristate "TIO PPS signal generator" depends on X86 && CPU_SUP_INTEL diff --git a/drivers/pps/generators/Makefile b/drivers/pps/generators/Makefile index e109920e8a2d..5d38774b4a56 100644 --- a/drivers/pps/generators/Makefile +++ b/drivers/pps/generators/Makefile @@ -7,7 +7,6 @@ pps_gen_core-y := pps_gen.o sysfs.o obj-$(CONFIG_PPS_GENERATOR) := pps_gen_core.o obj-$(CONFIG_PPS_GENERATOR_DUMMY) += pps_gen-dummy.o -obj-$(CONFIG_PPS_GENERATOR_PARPORT) += pps_gen_parport.o obj-$(CONFIG_PPS_GENERATOR_TIO) += pps_gen_tio.o ccflags-$(CONFIG_PPS_DEBUG) := -DDEBUG diff --git a/drivers/pps/generators/pps_gen_parport.c b/drivers/pps/generators/pps_gen_parport.c deleted file mode 100644 index 05bbf8d30ef1..000000000000 --- a/drivers/pps/generators/pps_gen_parport.c +++ /dev/null @@ -1,238 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * pps_gen_parport.c -- kernel parallel port PPS signal generator - * - * Copyright (C) 2009 Alexander Gordeev - */ - - -/* - * TODO: - * fix issues when realtime clock is adjusted in a leap - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include - -#define SIGNAL 0 -#define NO_SIGNAL PARPORT_CONTROL_STROBE - -/* module parameters */ - -#define SEND_DELAY_MAX 100000 - -static unsigned int send_delay = 30000; -MODULE_PARM_DESC(delay, - "Delay between setting and dropping the signal (ns)"); -module_param_named(delay, send_delay, uint, 0); - - -#define SAFETY_INTERVAL 3000 /* set the hrtimer earlier for safety (ns) */ - -/* internal per port structure */ -struct pps_generator_pp { - struct pardevice *pardev; /* parport device */ - struct hrtimer timer; - long port_write_time; /* calibrated port write time (ns) */ -}; - -static struct pps_generator_pp device = { - .pardev = NULL, -}; - -static int attached; - -/* calibrated time between a hrtimer event and the reaction */ -static long hrtimer_error = SAFETY_INTERVAL; - -/* the kernel hrtimer event */ -static enum hrtimer_restart hrtimer_event(struct hrtimer *timer) -{ - struct timespec64 expire_time, ts1, ts2, ts3, dts; - struct pps_generator_pp *dev; - struct parport *port; - long lim, delta; - unsigned long flags; - - /* We have to disable interrupts here. The idea is to prevent - * other interrupts on the same processor to introduce random - * lags while polling the clock. ktime_get_real_ts64() takes <1us on - * most machines while other interrupt handlers can take much - * more potentially. - * - * NB: approx time with blocked interrupts = - * send_delay + 3 * SAFETY_INTERVAL - */ - local_irq_save(flags); - - /* first of all we get the time stamp... */ - ktime_get_real_ts64(&ts1); - expire_time = ktime_to_timespec64(hrtimer_get_softexpires(timer)); - dev = container_of(timer, struct pps_generator_pp, timer); - lim = NSEC_PER_SEC - send_delay - dev->port_write_time; - - /* check if we are late */ - if (expire_time.tv_sec != ts1.tv_sec || ts1.tv_nsec > lim) { - local_irq_restore(flags); - pr_err("we are late this time %ptSp\n", &ts1); - goto done; - } - - /* busy loop until the time is right for an assert edge */ - do { - ktime_get_real_ts64(&ts2); - } while (expire_time.tv_sec == ts2.tv_sec && ts2.tv_nsec < lim); - - /* set the signal */ - port = dev->pardev->port; - port->ops->write_control(port, SIGNAL); - - /* busy loop until the time is right for a clear edge */ - lim = NSEC_PER_SEC - dev->port_write_time; - do { - ktime_get_real_ts64(&ts2); - } while (expire_time.tv_sec == ts2.tv_sec && ts2.tv_nsec < lim); - - /* unset the signal */ - port->ops->write_control(port, NO_SIGNAL); - - ktime_get_real_ts64(&ts3); - - local_irq_restore(flags); - - /* update calibrated port write time */ - dts = timespec64_sub(ts3, ts2); - dev->port_write_time = - (dev->port_write_time + timespec64_to_ns(&dts)) >> 1; - -done: - /* update calibrated hrtimer error */ - dts = timespec64_sub(ts1, expire_time); - delta = timespec64_to_ns(&dts); - /* If the new error value is bigger then the old, use the new - * value, if not then slowly move towards the new value. This - * way it should be safe in bad conditions and efficient in - * good conditions. - */ - if (delta >= hrtimer_error) - hrtimer_error = delta; - else - hrtimer_error = (3 * hrtimer_error + delta) >> 2; - - /* update the hrtimer expire time */ - hrtimer_set_expires(timer, - ktime_set(expire_time.tv_sec + 1, - NSEC_PER_SEC - (send_delay + - dev->port_write_time + SAFETY_INTERVAL + - 2 * hrtimer_error))); - - return HRTIMER_RESTART; -} - -/* calibrate port write time */ -#define PORT_NTESTS_SHIFT 5 -static void calibrate_port(struct pps_generator_pp *dev) -{ - struct parport *port = dev->pardev->port; - int i; - long acc = 0; - - for (i = 0; i < (1 << PORT_NTESTS_SHIFT); i++) { - struct timespec64 a, b; - unsigned long irq_flags; - - local_irq_save(irq_flags); - ktime_get_real_ts64(&a); - port->ops->write_control(port, NO_SIGNAL); - ktime_get_real_ts64(&b); - local_irq_restore(irq_flags); - - b = timespec64_sub(b, a); - acc += timespec64_to_ns(&b); - } - - dev->port_write_time = acc >> PORT_NTESTS_SHIFT; - pr_info("port write takes %ldns\n", dev->port_write_time); -} - -static inline ktime_t next_intr_time(struct pps_generator_pp *dev) -{ - struct timespec64 ts; - - ktime_get_real_ts64(&ts); - - return ktime_set(ts.tv_sec + - ((ts.tv_nsec > 990 * NSEC_PER_MSEC) ? 1 : 0), - NSEC_PER_SEC - (send_delay + - dev->port_write_time + 3 * SAFETY_INTERVAL)); -} - -static void parport_attach(struct parport *port) -{ - struct pardev_cb pps_cb; - - if (send_delay > SEND_DELAY_MAX) { - pr_err("delay value should be not greater then %d\n", SEND_DELAY_MAX); - return; - } - - if (attached) { - /* we already have a port */ - return; - } - - memset(&pps_cb, 0, sizeof(pps_cb)); - pps_cb.private = &device; - pps_cb.flags = PARPORT_FLAG_EXCL; - device.pardev = parport_register_dev_model(port, KBUILD_MODNAME, - &pps_cb, 0); - if (!device.pardev) { - pr_err("couldn't register with %s\n", port->name); - return; - } - - if (parport_claim_or_block(device.pardev) < 0) { - pr_err("couldn't claim %s\n", port->name); - goto err_unregister_dev; - } - - pr_info("attached to %s\n", port->name); - attached = 1; - - calibrate_port(&device); - - hrtimer_setup(&device.timer, hrtimer_event, CLOCK_REALTIME, HRTIMER_MODE_ABS); - hrtimer_start(&device.timer, next_intr_time(&device), HRTIMER_MODE_ABS); - - return; - -err_unregister_dev: - parport_unregister_device(device.pardev); -} - -static void parport_detach(struct parport *port) -{ - if (port->cad != device.pardev) - return; /* not our port */ - - hrtimer_cancel(&device.timer); - parport_release(device.pardev); - parport_unregister_device(device.pardev); -} - -static struct parport_driver pps_gen_parport_driver = { - .name = KBUILD_MODNAME, - .match_port = parport_attach, - .detach = parport_detach, -}; -module_parport_driver(pps_gen_parport_driver); - -MODULE_AUTHOR("Alexander Gordeev "); -MODULE_DESCRIPTION("parallel port PPS signal generator"); -MODULE_LICENSE("GPL"); From 79cb49ebb43ec39b4d9156e069fcc745ecbeedd0 Mon Sep 17 00:00:00 2001 From: Henry Zhang Date: Mon, 26 Jan 2026 15:17:07 -0500 Subject: [PATCH 233/297] speakup: Clarify bleep_time unit is milliseconds The documentation had a TODO questioning whether the unit was in jiffies. The implementation in drivers/accessibility/speakup/main.c passes spk_bleep_time to msecs_to_jiffies(), confirming the unit is in milliseconds. Signed-off-by: Henry Zhang Reviewed-by: Samuel Thibault Link: https://patch.msgid.link/20260126201707.1297665-1-zeri@umich.edu Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/stable/sysfs-driver-speakup | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Documentation/ABI/stable/sysfs-driver-speakup b/Documentation/ABI/stable/sysfs-driver-speakup index bcb6831aa114..8b508b4a7a00 100644 --- a/Documentation/ABI/stable/sysfs-driver-speakup +++ b/Documentation/ABI/stable/sysfs-driver-speakup @@ -23,8 +23,7 @@ What: /sys/accessibility/speakup/bleep_time KernelVersion: 2.6 Contact: speakup@linux-speakup.org Description: This controls the duration of the PC speaker beeps speakup - produces. - TODO: What are the units? Jiffies? + produces, in milliseconds. What: /sys/accessibility/speakup/cursor_time KernelVersion: 2.6 From 3fcd9a0fbb7dfbad3caa5057054d21b903157079 Mon Sep 17 00:00:00 2001 From: Patrick Wicki Date: Tue, 20 Jan 2026 14:06:02 +0100 Subject: [PATCH 234/297] eeprom: at25: add support for Infineon Cypress QSN FRAMs Add support for Infineon Cypress CY15****QSN FRAM chips. Unlike the QN variants these chips have an 8 byte JEDEC ID. The layout of the serial number matches that of already supported chips like the CY15B204QN, so make the read-out unconditional. Tested with the CY15B204QSN. According to Infineon datasheets, all QSN variants appear to share a consistent pattern so the size should be correctly detected based on the density bits in the ID: CY15B201QSN: 0x00 00 00 00 06 82 54 40, density_id: 8: 1Mb CY15B102QSN: 0x00 00 00 00 06 82 51 48, density_id: 9: 2Mb CY15B204QSN: 0x00 00 00 00 06 82 54 50, density_id: 10: 4Mb CY15V108QSN: 0x00 00 00 00 06 82 52 58, density_id: 11: 8Mb Signed-off-by: Patrick Wicki Reviewed-by: Alexander Sverdlin Tested-by: Alexander Sverdlin Link: https://patch.msgid.link/20260120130603.1066559-1-patrick@subset.ch Signed-off-by: Greg Kroah-Hartman --- drivers/misc/eeprom/at25.c | 52 +++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c index 883dfd0ed658..5b00921d07d2 100644 --- a/drivers/misc/eeprom/at25.c +++ b/drivers/misc/eeprom/at25.c @@ -398,30 +398,40 @@ static int at25_fram_to_chip(struct device *dev, struct spi_eeprom *chip) id[i] = id[j]; id[j] = tmp; } - if (id[6] != 0xc2) { - dev_err(dev, "Error: no Cypress FRAM with device ID (manufacturer ID bank 7: %02x)\n", id[6]); + + if (id[6] == 0xc2) { + switch (id[7]) { + case 0x21 ... 0x26: + chip->byte_len = BIT(id[7] - 0x21 + 4) * 1024; + break; + case 0x2a ... 0x30: + /* CY15B102QN ... CY15B116QN */ + chip->byte_len = BIT(((id[7] >> 1) & 0xf) + 13); + break; + default: + dev_err(dev, "Error: unsupported size (id %02x)\n", id[7]); + return -ENODEV; + } + } else if (id[2] == 0x82 && id[3] == 0x06) { + switch (id[1]) { + case 0x51 ... 0x54: + /* CY15B102QSN ... CY15B204QSN */ + chip->byte_len = BIT(((id[0] >> 3) & 0x1F) + 9); + break; + default: + dev_err(dev, "Error: unsupported product id %02x\n", id[1]); + return -ENODEV; + } + } else { + dev_err(dev, "Error: unrecognized JEDEC ID format: %*ph\n", + FM25_ID_LEN, id); return -ENODEV; } - switch (id[7]) { - case 0x21 ... 0x26: - chip->byte_len = BIT(id[7] - 0x21 + 4) * 1024; - break; - case 0x2a ... 0x30: - /* CY15B102QN ... CY15B116QN */ - chip->byte_len = BIT(((id[7] >> 1) & 0xf) + 13); - break; - default: - dev_err(dev, "Error: unsupported size (id %02x)\n", id[7]); - return -ENODEV; - } - - if (id[8]) { - fm25_aux_read(at25, sernum, FM25_RDSN, FM25_SN_LEN); - /* Swap byte order */ - for (i = 0; i < FM25_SN_LEN; i++) - at25->sernum[i] = sernum[FM25_SN_LEN - 1 - i]; - } + fm25_aux_read(at25, sernum, FM25_RDSN, FM25_SN_LEN); + /* Swap byte order */ + for (i = 0; i < FM25_SN_LEN; i++) + at25->sernum[i] = sernum[FM25_SN_LEN - 1 - i]; } if (chip->byte_len > 64 * 1024) From 62bb2054f9e84ec89c416d4558dbd574c54beddf Mon Sep 17 00:00:00 2001 From: Patrick Wicki Date: Tue, 20 Jan 2026 14:06:03 +0100 Subject: [PATCH 235/297] eeprom: at25: expose JEDEC ID via sysfs Return the raw JEDEC ID bytes as returned by the RDID command, even for variations that have the bytes in reverse order. This way we can avoid ambiguity if the manufacturer ever releases a new chip that returns them according to standard. Signed-off-by: Patrick Wicki Reviewed-by: Alexander Sverdlin Tested-by: Alexander Sverdlin Link: https://patch.msgid.link/20260120130603.1066559-2-patrick@subset.ch Signed-off-by: Greg Kroah-Hartman --- .../ABI/testing/sysfs-class-spi-eeprom | 11 ++++++ drivers/misc/eeprom/at25.c | 38 +++++++++++++++---- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-spi-eeprom b/Documentation/ABI/testing/sysfs-class-spi-eeprom index 1ff757982079..f4bc7d9454cf 100644 --- a/Documentation/ABI/testing/sysfs-class-spi-eeprom +++ b/Documentation/ABI/testing/sysfs-class-spi-eeprom @@ -17,3 +17,14 @@ Description: from the device. This is a read-only attribute. + +What: /sys/class/spi_master/spi/spi./jedec_id +Date: January 2026 +KernelVersion: 6.19 +Contact: Patrick Wicki +Description: + Contains the raw JEDEC ID bytes returned by the RDID (0x9f) command. The + bytes are exposed as a hex string in big-endian order as read from the + device. + + This is a read-only attribute. diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c index 5b00921d07d2..bc2cfb75d9bb 100644 --- a/drivers/misc/eeprom/at25.c +++ b/drivers/misc/eeprom/at25.c @@ -34,6 +34,7 @@ */ #define FM25_SN_LEN 8 /* serial number length */ +#define FM25_MAX_ID_LEN 9 /* ID length */ #define EE_MAXADDRLEN 3 /* 24 bit addresses, up to 2 MBytes */ struct at25_data { @@ -44,6 +45,8 @@ struct at25_data { struct nvmem_config nvmem_config; struct nvmem_device *nvmem; u8 sernum[FM25_SN_LEN]; + u8 id[FM25_MAX_ID_LEN]; + u8 id_len; }; #define AT25_WREN 0x06 /* latch the write enable */ @@ -64,8 +67,6 @@ struct at25_data { #define AT25_INSTR_BIT3 0x08 /* additional address bit in instr */ -#define FM25_ID_LEN 9 /* ID length */ - /* * Specs often allow 5ms for a page write, sometimes 20ms; * it's important to recover from write timeouts. @@ -180,11 +181,25 @@ static ssize_t sernum_show(struct device *dev, struct device_attribute *attr, ch } static DEVICE_ATTR_RO(sernum); -static struct attribute *sernum_attrs[] = { +static ssize_t jedec_id_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct at25_data *at25; + + at25 = dev_get_drvdata(dev); + + if (!at25->id_len) + return -EOPNOTSUPP; + + return sysfs_emit(buf, "%*phN\n", at25->id_len, at25->id); +} +static DEVICE_ATTR_RO(jedec_id); + +static struct attribute *at25_attrs[] = { &dev_attr_sernum.attr, + &dev_attr_jedec_id.attr, NULL, }; -ATTRIBUTE_GROUPS(sernum); +ATTRIBUTE_GROUPS(at25); /* * Poll Read Status Register with timeout @@ -378,7 +393,7 @@ static int at25_fram_to_chip(struct device *dev, struct spi_eeprom *chip) { struct at25_data *at25 = container_of(chip, struct at25_data, chip); u8 sernum[FM25_SN_LEN]; - u8 id[FM25_ID_LEN]; + u8 id[FM25_MAX_ID_LEN]; u32 val; int i; @@ -388,7 +403,12 @@ static int at25_fram_to_chip(struct device *dev, struct spi_eeprom *chip) chip->byte_len = val; } else { /* Get ID of chip */ - fm25_aux_read(at25, id, FM25_RDID, FM25_ID_LEN); + fm25_aux_read(at25, id, FM25_RDID, FM25_MAX_ID_LEN); + + /* Store the unprocessed ID for exposing via sysfs */ + memcpy(at25->id, id, FM25_MAX_ID_LEN); + at25->id_len = FM25_MAX_ID_LEN; + /* There are inside-out FRAM variations, detect them and reverse the ID bytes */ if (id[6] == 0x7f && id[2] == 0xc2) for (i = 0; i < ARRAY_SIZE(id) / 2; i++) { @@ -400,6 +420,7 @@ static int at25_fram_to_chip(struct device *dev, struct spi_eeprom *chip) } if (id[6] == 0xc2) { + at25->id_len = 9; switch (id[7]) { case 0x21 ... 0x26: chip->byte_len = BIT(id[7] - 0x21 + 4) * 1024; @@ -413,6 +434,7 @@ static int at25_fram_to_chip(struct device *dev, struct spi_eeprom *chip) return -ENODEV; } } else if (id[2] == 0x82 && id[3] == 0x06) { + at25->id_len = 8; switch (id[1]) { case 0x51 ... 0x54: /* CY15B102QSN ... CY15B204QSN */ @@ -424,7 +446,7 @@ static int at25_fram_to_chip(struct device *dev, struct spi_eeprom *chip) } } else { dev_err(dev, "Error: unrecognized JEDEC ID format: %*ph\n", - FM25_ID_LEN, id); + FM25_MAX_ID_LEN, id); return -ENODEV; } @@ -549,7 +571,7 @@ static struct spi_mem_driver at25_driver = { .driver = { .name = "at25", .of_match_table = at25_of_match, - .dev_groups = sernum_groups, + .dev_groups = at25_groups, }, .id_table = at25_spi_ids, }, From c9627831fb60300e4fe46467e8c36379c7c3313a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:47:30 +0000 Subject: [PATCH 236/297] gpib: agilent_82350b: Unify *allocate_private usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the return value of agilent_82350b_allocate_private in calling code as early return value in case of error. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-2-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/agilent_82350b/agilent_82350b.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpib/agilent_82350b/agilent_82350b.c b/drivers/gpib/agilent_82350b/agilent_82350b.c index 01a5bb43cd2d..d55d097aa6f0 100644 --- a/drivers/gpib/agilent_82350b/agilent_82350b.c +++ b/drivers/gpib/agilent_82350b/agilent_82350b.c @@ -599,8 +599,9 @@ static int agilent_82350b_generic_attach(struct gpib_board *board, board->status = 0; - if (agilent_82350b_allocate_private(board)) - return -ENOMEM; + retval = agilent_82350b_allocate_private(board); + if (retval) + return retval; a_priv = board->private_data; a_priv->using_fifos = use_fifos; tms_priv = &a_priv->tms9914_priv; From 048b9f44350a7fd1e73cd22f5d1f00cc208609fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:47:35 +0000 Subject: [PATCH 237/297] gpib: agilent_82357a: Fix the *allocate_private retval check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change if (retval < 0) return retval; into if (retval) return retval; as it is more fitting in this case. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-3-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/agilent_82357a/agilent_82357a.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpib/agilent_82357a/agilent_82357a.c b/drivers/gpib/agilent_82357a/agilent_82357a.c index 77c8e549b208..26d2830c3fa3 100644 --- a/drivers/gpib/agilent_82357a/agilent_82357a.c +++ b/drivers/gpib/agilent_82357a/agilent_82357a.c @@ -1316,7 +1316,7 @@ static int agilent_82357a_attach(struct gpib_board *board, const struct gpib_boa return -ERESTARTSYS; retval = agilent_82357a_allocate_private(board); - if (retval < 0) { + if (retval) { mutex_unlock(&agilent_82357a_hotplug_lock); return retval; } From 1ec138c8db699c5af354e230fbd73ab0aa3bbb72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:47:39 +0000 Subject: [PATCH 238/297] gpib: cb7210: Replace kmalloc/memset to kzalloc in *allocate_private MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace kmalloc/memset pair to kzalloc in cb7210_allocate_private. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-4-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/cb7210/cb7210.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpib/cb7210/cb7210.c b/drivers/gpib/cb7210/cb7210.c index 24c61b151071..e354666a5823 100644 --- a/drivers/gpib/cb7210/cb7210.c +++ b/drivers/gpib/cb7210/cb7210.c @@ -856,11 +856,10 @@ static int cb7210_allocate_private(struct gpib_board *board) { struct cb7210_priv *priv; - board->private_data = kmalloc(sizeof(struct cb7210_priv), GFP_KERNEL); + board->private_data = kzalloc(sizeof(struct cb7210_priv), GFP_KERNEL); if (!board->private_data) return -ENOMEM; priv = board->private_data; - memset(priv, 0, sizeof(struct cb7210_priv)); init_nec7210_private(&priv->nec7210_priv); return 0; } From 0ea001af4e169ff026c331b9e6eeb5cc09c07c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:47:44 +0000 Subject: [PATCH 239/297] gpib: cb7210: Unify *allocate_private usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the return value of cb7210_allocate_private in calling code as early return value in case of error. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-5-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/cb7210/cb7210.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpib/cb7210/cb7210.c b/drivers/gpib/cb7210/cb7210.c index e354666a5823..e9d5fd19b495 100644 --- a/drivers/gpib/cb7210/cb7210.c +++ b/drivers/gpib/cb7210/cb7210.c @@ -875,11 +875,13 @@ static int cb7210_generic_attach(struct gpib_board *board) { struct cb7210_priv *cb_priv; struct nec7210_priv *nec_priv; + int retval; board->status = 0; - if (cb7210_allocate_private(board)) - return -ENOMEM; + retval = cb7210_allocate_private(board); + if (retval) + return retval; cb_priv = board->private_data; nec_priv = &cb_priv->nec7210_priv; nec_priv->read_byte = nec7210_locking_ioport_read_byte; From 258dd4c7ddee78128f3229936044f8bdea8e2f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:47:49 +0000 Subject: [PATCH 240/297] gpib: cec: Replace kmalloc/memset to kzalloc in *allocate_private MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace kmalloc/memset pair to kzalloc in cec_allocate_private. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-6-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/cec/cec_gpib.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpib/cec/cec_gpib.c b/drivers/gpib/cec/cec_gpib.c index dbf9b95baabc..cb81c2934d60 100644 --- a/drivers/gpib/cec/cec_gpib.c +++ b/drivers/gpib/cec/cec_gpib.c @@ -220,11 +220,10 @@ static int cec_allocate_private(struct gpib_board *board) { struct cec_priv *priv; - board->private_data = kmalloc(sizeof(struct cec_priv), GFP_KERNEL); + board->private_data = kzalloc(sizeof(struct cec_priv), GFP_KERNEL); if (!board->private_data) return -1; priv = board->private_data; - memset(priv, 0, sizeof(struct cec_priv)); init_nec7210_private(&priv->nec7210_priv); return 0; } From b47077a4c03e50b14a03633773bf549cb2f0a38f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:47:54 +0000 Subject: [PATCH 241/297] gpib: cec: Unify *allocate_private return value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Return -ENOMEM instead of -1 in cec_allocate_private in case of memory allocation failure. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-7-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/cec/cec_gpib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpib/cec/cec_gpib.c b/drivers/gpib/cec/cec_gpib.c index cb81c2934d60..c2129ba3a902 100644 --- a/drivers/gpib/cec/cec_gpib.c +++ b/drivers/gpib/cec/cec_gpib.c @@ -222,7 +222,7 @@ static int cec_allocate_private(struct gpib_board *board) board->private_data = kzalloc(sizeof(struct cec_priv), GFP_KERNEL); if (!board->private_data) - return -1; + return -ENOMEM; priv = board->private_data; init_nec7210_private(&priv->nec7210_priv); return 0; From 908206d82e45f190d44ac6cdee70cd7676c757a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:48:00 +0000 Subject: [PATCH 242/297] gpib: cec: Unify *allocate_private usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the return value of cec_allocate_private in calling code as early return value in case of error. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-8-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/cec/cec_gpib.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpib/cec/cec_gpib.c b/drivers/gpib/cec/cec_gpib.c index c2129ba3a902..7e57d65d6c32 100644 --- a/drivers/gpib/cec/cec_gpib.c +++ b/drivers/gpib/cec/cec_gpib.c @@ -238,11 +238,13 @@ static int cec_generic_attach(struct gpib_board *board) { struct cec_priv *cec_priv; struct nec7210_priv *nec_priv; + int retval; board->status = 0; - if (cec_allocate_private(board)) - return -ENOMEM; + retval = cec_allocate_private(board); + if (retval) + return retval; cec_priv = board->private_data; nec_priv = &cec_priv->nec7210_priv; nec_priv->read_byte = nec7210_ioport_read_byte; From 59e2e6b5620f689ebc9967dda744d5f1d90180b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:48:03 +0000 Subject: [PATCH 243/297] gpib: eastwood: Replace kmalloc/memset to kzalloc in *allocate_private MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace kmalloc/memset pair to kzalloc in fluke_allocate_private. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-9-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/eastwood/fluke_gpib.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpib/eastwood/fluke_gpib.c b/drivers/gpib/eastwood/fluke_gpib.c index 3ae848e3f738..8e315b0e35a2 100644 --- a/drivers/gpib/eastwood/fluke_gpib.c +++ b/drivers/gpib/eastwood/fluke_gpib.c @@ -853,11 +853,10 @@ static int fluke_allocate_private(struct gpib_board *board) { struct fluke_priv *priv; - board->private_data = kmalloc(sizeof(struct fluke_priv), GFP_KERNEL); + board->private_data = kzalloc(sizeof(struct fluke_priv), GFP_KERNEL); if (!board->private_data) return -ENOMEM; priv = board->private_data; - memset(priv, 0, sizeof(struct fluke_priv)); init_nec7210_private(&priv->nec7210_priv); priv->dma_buffer_size = 0x7ff; priv->dma_buffer = kmalloc(priv->dma_buffer_size, GFP_KERNEL); From f9d2893ff9e8871bac046c569a794f47fba3bae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:48:05 +0000 Subject: [PATCH 244/297] gpib: eastwood: Fix the *allocate_private retval check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change if (retval < 0) return retval; into if (retval) return retval; as it is more fitting in this case. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-10-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/eastwood/fluke_gpib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpib/eastwood/fluke_gpib.c b/drivers/gpib/eastwood/fluke_gpib.c index 8e315b0e35a2..56153b8a1cb2 100644 --- a/drivers/gpib/eastwood/fluke_gpib.c +++ b/drivers/gpib/eastwood/fluke_gpib.c @@ -886,7 +886,7 @@ static int fluke_generic_attach(struct gpib_board *board) board->status = 0; retval = fluke_allocate_private(board); - if (retval < 0) + if (retval) return retval; e_priv = board->private_data; nec_priv = &e_priv->nec7210_priv; From 578481c343fabbf8514ab866e5511b7685fdad93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:48:09 +0000 Subject: [PATCH 245/297] gpib: fmh_gpib: Replace kmalloc/memset to kzalloc in *allocate_private MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace kmalloc/memset pair to kzalloc in fmh_gpib_allocate_private. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-11-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/fmh_gpib/fmh_gpib.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpib/fmh_gpib/fmh_gpib.c b/drivers/gpib/fmh_gpib/fmh_gpib.c index f7bfb4a8e553..b8d955f124bd 100644 --- a/drivers/gpib/fmh_gpib/fmh_gpib.c +++ b/drivers/gpib/fmh_gpib/fmh_gpib.c @@ -1250,11 +1250,10 @@ static int fmh_gpib_allocate_private(struct gpib_board *board) { struct fmh_priv *priv; - board->private_data = kmalloc(sizeof(struct fmh_priv), GFP_KERNEL); + board->private_data = kzalloc(sizeof(struct fmh_priv), GFP_KERNEL); if (!board->private_data) return -ENOMEM; priv = board->private_data; - memset(priv, 0, sizeof(struct fmh_priv)); init_nec7210_private(&priv->nec7210_priv); priv->dma_buffer_size = 0x800; priv->dma_buffer = kmalloc(priv->dma_buffer_size, GFP_KERNEL); From c47b98c47612a9fbe25d7331235b17a195e79f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:48:14 +0000 Subject: [PATCH 246/297] gpib: fmh_gpib: Fix the *allocate_private retval check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change if (retval < 0) return retval; into if (retval) return retval; as it is more fitting in this case. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-12-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/fmh_gpib/fmh_gpib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpib/fmh_gpib/fmh_gpib.c b/drivers/gpib/fmh_gpib/fmh_gpib.c index b8d955f124bd..d82ec06b3085 100644 --- a/drivers/gpib/fmh_gpib/fmh_gpib.c +++ b/drivers/gpib/fmh_gpib/fmh_gpib.c @@ -1285,7 +1285,7 @@ static int fmh_gpib_generic_attach(struct gpib_board *board) board->status = 0; retval = fmh_gpib_allocate_private(board); - if (retval < 0) + if (retval) return retval; e_priv = board->private_data; nec_priv = &e_priv->nec7210_priv; From ad161c8b12100cbe80980be03e03d324f7d45fc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:48:19 +0000 Subject: [PATCH 247/297] gpib: gpio: Unify *allocate_private return value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Return -ENOMEM instead of -1 in allocate_private in case of memory allocation failure. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-13-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/gpio/gpib_bitbang.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpib/gpio/gpib_bitbang.c b/drivers/gpib/gpio/gpib_bitbang.c index 374cd61355e9..62adcab1ddfb 100644 --- a/drivers/gpib/gpio/gpib_bitbang.c +++ b/drivers/gpib/gpio/gpib_bitbang.c @@ -1068,7 +1068,7 @@ static int allocate_private(struct gpib_board *board) { board->private_data = kzalloc(sizeof(struct bb_priv), GFP_KERNEL); if (!board->private_data) - return -1; + return -ENOMEM; return 0; } From 1dd1bc4d79431e3cb0b8581882bd4afa57443d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:48:24 +0000 Subject: [PATCH 248/297] gpib: gpio: Unify *allocate_private usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the return value of allocate_private in calling code as early return value in case of error. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-14-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/gpio/gpib_bitbang.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/gpib/gpio/gpib_bitbang.c b/drivers/gpib/gpio/gpib_bitbang.c index 62adcab1ddfb..ba99d6c95ddf 100644 --- a/drivers/gpib/gpio/gpib_bitbang.c +++ b/drivers/gpib/gpio/gpib_bitbang.c @@ -1205,14 +1205,15 @@ static void bb_detach(struct gpib_board *board) static int bb_attach(struct gpib_board *board, const struct gpib_board_config *config) { struct bb_priv *priv; - int retval = 0; + int retval; dbg_printk(2, "%s\n", "Enter ..."); board->status = 0; - if (allocate_private(board)) - return -ENOMEM; + retval = allocate_private(board); + if (retval) + return retval; priv = board->private_data; priv->direction = -1; priv->t1_delay = 2000; From 0a1e9b99d83ae34955c909e3c5ffbaf3ad3028a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:48:28 +0000 Subject: [PATCH 249/297] gpib: hp_82335: Unify *allocate_private return value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Return -ENOMEM instead of -1 in hp82335_allocate_private in case of memory allocation failure. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-15-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/hp_82335/hp82335.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpib/hp_82335/hp82335.c b/drivers/gpib/hp_82335/hp82335.c index d0e47ef77c87..6717fa437d31 100644 --- a/drivers/gpib/hp_82335/hp82335.c +++ b/drivers/gpib/hp_82335/hp82335.c @@ -212,7 +212,7 @@ static int hp82335_allocate_private(struct gpib_board *board) { board->private_data = kzalloc(sizeof(struct hp82335_priv), GFP_KERNEL); if (!board->private_data) - return -1; + return -ENOMEM; return 0; } From c0790b6c901e6f33aa7a6d6373bf77912fa75865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:48:32 +0000 Subject: [PATCH 250/297] gpib: hp_82335: Unify *allocate_private usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the return value of hp82335_allocate_private in calling code as early return value in case of error. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-16-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/hp_82335/hp82335.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpib/hp_82335/hp82335.c b/drivers/gpib/hp_82335/hp82335.c index 6717fa437d31..9baf1d3b6751 100644 --- a/drivers/gpib/hp_82335/hp82335.c +++ b/drivers/gpib/hp_82335/hp82335.c @@ -253,8 +253,9 @@ static int hp82335_attach(struct gpib_board *board, const struct gpib_board_conf board->status = 0; - if (hp82335_allocate_private(board)) - return -ENOMEM; + retval = hp82335_allocate_private(board); + if (retval) + return retval; hp_priv = board->private_data; tms_priv = &hp_priv->tms9914_priv; tms_priv->read_byte = hp82335_read_byte; From 3f5d83160037ac9a055b8375f717a206497b847f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:48:35 +0000 Subject: [PATCH 251/297] gpib: hp_82341: Unify *allocate_private usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the return value of hp_82341_allocate_private in calling code as early return value in case of error. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-17-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/hp_82341/hp_82341.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpib/hp_82341/hp_82341.c b/drivers/gpib/hp_82341/hp_82341.c index 1a2ad0560e14..5aaf5b15b98b 100644 --- a/drivers/gpib/hp_82341/hp_82341.c +++ b/drivers/gpib/hp_82341/hp_82341.c @@ -693,8 +693,9 @@ static int hp_82341_attach(struct gpib_board *board, const struct gpib_board_con int retval; board->status = 0; - if (hp_82341_allocate_private(board)) - return -ENOMEM; + retval = hp_82341_allocate_private(board); + if (retval) + return retval; hp_priv = board->private_data; tms_priv = &hp_priv->tms9914_priv; tms_priv->read_byte = hp_82341_read_byte; From b3d3ab10b9b3919f4fd62b8eef4546e24f2d7bd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:48:39 +0000 Subject: [PATCH 252/297] gpib: ines: Replace kmalloc/memset to kzalloc in *allocate_private MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace kmalloc/memset pair to kzalloc in ines_allocate_private. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-18-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/ines/ines_gpib.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpib/ines/ines_gpib.c b/drivers/gpib/ines/ines_gpib.c index a3cf846fd0f9..bab11b50adaa 100644 --- a/drivers/gpib/ines/ines_gpib.c +++ b/drivers/gpib/ines/ines_gpib.c @@ -657,11 +657,10 @@ static int ines_allocate_private(struct gpib_board *board) { struct ines_priv *priv; - board->private_data = kmalloc(sizeof(struct ines_priv), GFP_KERNEL); + board->private_data = kzalloc(sizeof(struct ines_priv), GFP_KERNEL); if (!board->private_data) return -1; priv = board->private_data; - memset(priv, 0, sizeof(struct ines_priv)); init_nec7210_private(&priv->nec7210_priv); return 0; } From 24d4d06acb6f1433bfe814b40312e3d2d385a3b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:48:45 +0000 Subject: [PATCH 253/297] gpib: ines: Unify *allocate_private return value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Return -ENOMEM instead of -1 in ines_allocate_private in case of memory allocation failure. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-19-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/ines/ines_gpib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpib/ines/ines_gpib.c b/drivers/gpib/ines/ines_gpib.c index bab11b50adaa..a795b12ef8e0 100644 --- a/drivers/gpib/ines/ines_gpib.c +++ b/drivers/gpib/ines/ines_gpib.c @@ -659,7 +659,7 @@ static int ines_allocate_private(struct gpib_board *board) board->private_data = kzalloc(sizeof(struct ines_priv), GFP_KERNEL); if (!board->private_data) - return -1; + return -ENOMEM; priv = board->private_data; init_nec7210_private(&priv->nec7210_priv); return 0; From 11f1b16916222993cf54011541b12c80e6c909aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:48:51 +0000 Subject: [PATCH 254/297] gpib: ines: Unify *allocate_private usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the return value of ines_allocate_private in calling code as early return value in case of error. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-20-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/ines/ines_gpib.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpib/ines/ines_gpib.c b/drivers/gpib/ines/ines_gpib.c index a795b12ef8e0..737de5fef4b3 100644 --- a/drivers/gpib/ines/ines_gpib.c +++ b/drivers/gpib/ines/ines_gpib.c @@ -675,11 +675,13 @@ static int ines_generic_attach(struct gpib_board *board) { struct ines_priv *ines_priv; struct nec7210_priv *nec_priv; + int retval; board->status = 0; - if (ines_allocate_private(board)) - return -ENOMEM; + retval = ines_allocate_private(board); + if (retval) + return retval; ines_priv = board->private_data; nec_priv = &ines_priv->nec7210_priv; nec_priv->read_byte = nec7210_ioport_read_byte; From 64900aa878fc6a5f2a017374f869890749c9382e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:48:56 +0000 Subject: [PATCH 255/297] gpib: ni_usb: Replace kmalloc/memset to kzalloc in *allocate_private MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace kmalloc/memset pair to kzalloc in ni_usb_allocate_private. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-21-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/ni_usb/ni_usb_gpib.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpib/ni_usb/ni_usb_gpib.c b/drivers/gpib/ni_usb/ni_usb_gpib.c index b6fddb437f55..b062360fff00 100644 --- a/drivers/gpib/ni_usb/ni_usb_gpib.c +++ b/drivers/gpib/ni_usb/ni_usb_gpib.c @@ -1659,11 +1659,10 @@ static int ni_usb_allocate_private(struct gpib_board *board) { struct ni_usb_priv *ni_priv; - board->private_data = kmalloc(sizeof(struct ni_usb_priv), GFP_KERNEL); + board->private_data = kzalloc(sizeof(struct ni_usb_priv), GFP_KERNEL); if (!board->private_data) return -ENOMEM; ni_priv = board->private_data; - memset(ni_priv, 0, sizeof(struct ni_usb_priv)); mutex_init(&ni_priv->bulk_transfer_lock); mutex_init(&ni_priv->control_transfer_lock); mutex_init(&ni_priv->interrupt_transfer_lock); From 78047416f80007b25062bd4791d98e24c0aaa687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:48:59 +0000 Subject: [PATCH 256/297] gpib: ni_usb: Fix the *allocate_private retval check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change if (retval < 0) return retval; into if (retval) return retval; as it is more fitting in this case. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-22-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/ni_usb/ni_usb_gpib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpib/ni_usb/ni_usb_gpib.c b/drivers/gpib/ni_usb/ni_usb_gpib.c index b062360fff00..c6f1a70d44f5 100644 --- a/drivers/gpib/ni_usb/ni_usb_gpib.c +++ b/drivers/gpib/ni_usb/ni_usb_gpib.c @@ -2234,7 +2234,7 @@ static int ni_usb_attach(struct gpib_board *board, const struct gpib_board_confi mutex_lock(&ni_usb_hotplug_lock); retval = ni_usb_allocate_private(board); - if (retval < 0) { + if (retval) { mutex_unlock(&ni_usb_hotplug_lock); return retval; } From 6e6dc3f7c08f17b2b146eeb49fc674fac8bc3b10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:49:03 +0000 Subject: [PATCH 257/297] gpib: pc2: Replace kmalloc/memset to kzalloc in *allocate_private MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace kmalloc/memset pair to kzalloc in allocate_private. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-23-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/pc2/pc2_gpib.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpib/pc2/pc2_gpib.c b/drivers/gpib/pc2/pc2_gpib.c index 9f3943d1df66..a7c0cf200e2e 100644 --- a/drivers/gpib/pc2/pc2_gpib.c +++ b/drivers/gpib/pc2/pc2_gpib.c @@ -237,11 +237,10 @@ static int allocate_private(struct gpib_board *board) { struct pc2_priv *priv; - board->private_data = kmalloc(sizeof(struct pc2_priv), GFP_KERNEL); + board->private_data = kzalloc(sizeof(struct pc2_priv), GFP_KERNEL); if (!board->private_data) return -1; priv = board->private_data; - memset(priv, 0, sizeof(struct pc2_priv)); init_nec7210_private(&priv->nec7210_priv); return 0; } From 68de22e9947c87a39557376260d35c81e63d6c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:49:09 +0000 Subject: [PATCH 258/297] gpib: pc2: Unify *allocate_private return value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Return -ENOMEM instead of -1 in allocate_private in case of memory allocation failure. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-24-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/pc2/pc2_gpib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpib/pc2/pc2_gpib.c b/drivers/gpib/pc2/pc2_gpib.c index a7c0cf200e2e..e850a781c836 100644 --- a/drivers/gpib/pc2/pc2_gpib.c +++ b/drivers/gpib/pc2/pc2_gpib.c @@ -239,7 +239,7 @@ static int allocate_private(struct gpib_board *board) board->private_data = kzalloc(sizeof(struct pc2_priv), GFP_KERNEL); if (!board->private_data) - return -1; + return -ENOMEM; priv = board->private_data; init_nec7210_private(&priv->nec7210_priv); return 0; From c2a9f77c0e03622880405f5699010b4835d2d619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:49:13 +0000 Subject: [PATCH 259/297] gpib: pc2: Unify *allocate_private usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the return value of allocate_private in calling code as early return value in case of error. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-25-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/pc2/pc2_gpib.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpib/pc2/pc2_gpib.c b/drivers/gpib/pc2/pc2_gpib.c index e850a781c836..1664861d360d 100644 --- a/drivers/gpib/pc2/pc2_gpib.c +++ b/drivers/gpib/pc2/pc2_gpib.c @@ -256,10 +256,12 @@ static int pc2_generic_attach(struct gpib_board *board, const struct gpib_board_ { struct pc2_priv *pc2_priv; struct nec7210_priv *nec_priv; + int retval; board->status = 0; - if (allocate_private(board)) - return -ENOMEM; + retval = allocate_private(board); + if (retval) + return retval; pc2_priv = board->private_data; nec_priv = &pc2_priv->nec7210_priv; nec_priv->read_byte = nec7210_ioport_read_byte; From a16ad9b68b10858fb69273ce2d01b03a930c180c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:49:16 +0000 Subject: [PATCH 260/297] gpib: tnt4882: Replace kmalloc/memset to kzalloc in *allocate_private MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace kmalloc/memset pair to kzalloc in tnt4882_allocate_private. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-26-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/tnt4882/tnt4882_gpib.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpib/tnt4882/tnt4882_gpib.c b/drivers/gpib/tnt4882/tnt4882_gpib.c index c03a976b7380..5f8d7547dadb 100644 --- a/drivers/gpib/tnt4882/tnt4882_gpib.c +++ b/drivers/gpib/tnt4882/tnt4882_gpib.c @@ -843,11 +843,10 @@ static int tnt4882_allocate_private(struct gpib_board *board) { struct tnt4882_priv *tnt_priv; - board->private_data = kmalloc(sizeof(struct tnt4882_priv), GFP_KERNEL); + board->private_data = kzalloc(sizeof(struct tnt4882_priv), GFP_KERNEL); if (!board->private_data) return -1; tnt_priv = board->private_data; - memset(tnt_priv, 0, sizeof(struct tnt4882_priv)); init_nec7210_private(&tnt_priv->nec7210_priv); return 0; } From 9effb86530ee61fe953a8a5d2c052fdc01c5d755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:49:19 +0000 Subject: [PATCH 261/297] gpib: tnt4882: Unify *allocate_private return value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Return -ENOMEM instead of -1 in tnt4882_allocate_private in case of memory allocation failure. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-27-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/tnt4882/tnt4882_gpib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpib/tnt4882/tnt4882_gpib.c b/drivers/gpib/tnt4882/tnt4882_gpib.c index 5f8d7547dadb..7b9cfb6c07ad 100644 --- a/drivers/gpib/tnt4882/tnt4882_gpib.c +++ b/drivers/gpib/tnt4882/tnt4882_gpib.c @@ -845,7 +845,7 @@ static int tnt4882_allocate_private(struct gpib_board *board) board->private_data = kzalloc(sizeof(struct tnt4882_priv), GFP_KERNEL); if (!board->private_data) - return -1; + return -ENOMEM; tnt_priv = board->private_data; init_nec7210_private(&tnt_priv->nec7210_priv); return 0; From 3df1fd31f6fcf0ed87093bca510e38f2d643036f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= Date: Fri, 16 Jan 2026 17:49:23 +0000 Subject: [PATCH 262/297] gpib: tnt4882: Unify *allocate_private usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the return value of tnt4882_allocate_private in calling code as early return value in case of error. Signed-off-by: Dominik Karol Piątkowski Link: https://patch.msgid.link/20260116174647.317256-28-dominik.karol.piatkowski@protonmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpib/tnt4882/tnt4882_gpib.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/gpib/tnt4882/tnt4882_gpib.c b/drivers/gpib/tnt4882/tnt4882_gpib.c index 7b9cfb6c07ad..6d241509419e 100644 --- a/drivers/gpib/tnt4882/tnt4882_gpib.c +++ b/drivers/gpib/tnt4882/tnt4882_gpib.c @@ -915,8 +915,9 @@ static int ni_pci_attach(struct gpib_board *board, const struct gpib_board_confi board->status = 0; - if (tnt4882_allocate_private(board)) - return -ENOMEM; + retval = tnt4882_allocate_private(board); + if (retval) + return retval; tnt_priv = board->private_data; nec_priv = &tnt_priv->nec7210_priv; nec_priv->type = TNT4882; @@ -1038,8 +1039,9 @@ static int ni_isa_attach_common(struct gpib_board *board, const struct gpib_boar board->status = 0; - if (tnt4882_allocate_private(board)) - return -ENOMEM; + retval = tnt4882_allocate_private(board); + if (retval) + return retval; tnt_priv = board->private_data; nec_priv = &tnt_priv->nec7210_priv; nec_priv->type = chipset; @@ -1724,8 +1726,9 @@ static int ni_pcmcia_attach(struct gpib_board *board, const struct gpib_board_co board->status = 0; - if (tnt4882_allocate_private(board)) - return -ENOMEM; + retval = tnt4882_allocate_private(board); + if (retval) + return retval; tnt_priv = board->private_data; nec_priv = &tnt_priv->nec7210_priv; From 94c37d42cb7ca362aee9633bec2dbeed787edf3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 9 Dec 2025 12:40:25 +0100 Subject: [PATCH 263/297] fsi: Make use of module_fsi_driver() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All other fsi drivers already use this helper to reduce boilerplate. Catch up for the last two remaining ones. Signed-off-by: Uwe Kleine-König Acked-by: Eddie James Link: https://patch.msgid.link/602437dc213cbe35ee75d0d0a4b4a2c28859e3f9.1765279318.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/fsi/fsi-sbefifo.c | 13 +------------ drivers/fsi/fsi-scom.c | 13 +------------ 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c index 0a98517f3959..5a08423d0c7e 100644 --- a/drivers/fsi/fsi-sbefifo.c +++ b/drivers/fsi/fsi-sbefifo.c @@ -1139,18 +1139,7 @@ static struct fsi_driver sbefifo_drv = { } }; -static int sbefifo_init(void) -{ - return fsi_driver_register(&sbefifo_drv); -} - -static void sbefifo_exit(void) -{ - fsi_driver_unregister(&sbefifo_drv); -} - -module_init(sbefifo_init); -module_exit(sbefifo_exit); +module_fsi_driver(sbefifo_drv); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Brad Bishop "); MODULE_AUTHOR("Eddie James "); diff --git a/drivers/fsi/fsi-scom.c b/drivers/fsi/fsi-scom.c index 411ddc018cd8..f533106085ac 100644 --- a/drivers/fsi/fsi-scom.c +++ b/drivers/fsi/fsi-scom.c @@ -613,17 +613,6 @@ static struct fsi_driver scom_drv = { } }; -static int scom_init(void) -{ - return fsi_driver_register(&scom_drv); -} - -static void scom_exit(void) -{ - fsi_driver_unregister(&scom_drv); -} - -module_init(scom_init); -module_exit(scom_exit); +module_fsi_driver(scom_drv); MODULE_DESCRIPTION("SCOM FSI Client device driver"); MODULE_LICENSE("GPL"); From 03db6a80b79ba0560ef882747b9282cd4eed37e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 9 Dec 2025 12:40:26 +0100 Subject: [PATCH 264/297] fsi: Assign driver's bus in fsi_driver_register() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of letting each driver assign the bus, do it once in the fsi driver register function. Simplify the fsi drivers that are living in drivers/fsi accordingly. Once all fsi drivers dropped assigning the bus, fsi_bus_type can be made a static variable. Signed-off-by: Uwe Kleine-König Acked-by: Eddie James Link: https://patch.msgid.link/54804c2cd4d84a6b5fb679831122b6acdd36b168.1765279318.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/fsi/fsi-core.c | 2 ++ drivers/fsi/fsi-master-hub.c | 1 - drivers/fsi/fsi-sbefifo.c | 1 - drivers/fsi/fsi-scom.c | 1 - drivers/fsi/i2cr-scom.c | 1 - 5 files changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c index c6c115993ebc..e1ea1124282e 100644 --- a/drivers/fsi/fsi-core.c +++ b/drivers/fsi/fsi-core.c @@ -1394,6 +1394,8 @@ int fsi_driver_register(struct fsi_driver *fsi_drv) if (!fsi_drv->id_table) return -EINVAL; + fsi_drv->drv.bus = &fsi_bus_type; + return driver_register(&fsi_drv->drv); } EXPORT_SYMBOL_GPL(fsi_driver_register); diff --git a/drivers/fsi/fsi-master-hub.c b/drivers/fsi/fsi-master-hub.c index 6568fed7db3c..d389856d18ac 100644 --- a/drivers/fsi/fsi-master-hub.c +++ b/drivers/fsi/fsi-master-hub.c @@ -288,7 +288,6 @@ static struct fsi_driver hub_master_driver = { .id_table = hub_master_ids, .drv = { .name = "fsi-master-hub", - .bus = &fsi_bus_type, .probe = hub_master_probe, .remove = hub_master_remove, } diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c index 5a08423d0c7e..fde1c34743a0 100644 --- a/drivers/fsi/fsi-sbefifo.c +++ b/drivers/fsi/fsi-sbefifo.c @@ -1133,7 +1133,6 @@ static struct fsi_driver sbefifo_drv = { .id_table = sbefifo_ids, .drv = { .name = DEVICE_NAME, - .bus = &fsi_bus_type, .probe = sbefifo_probe, .remove = sbefifo_remove, } diff --git a/drivers/fsi/fsi-scom.c b/drivers/fsi/fsi-scom.c index f533106085ac..2eda44451cc1 100644 --- a/drivers/fsi/fsi-scom.c +++ b/drivers/fsi/fsi-scom.c @@ -606,7 +606,6 @@ static struct fsi_driver scom_drv = { .id_table = scom_ids, .drv = { .name = "scom", - .bus = &fsi_bus_type, .of_match_table = scom_of_ids, .probe = scom_probe, .remove = scom_remove, diff --git a/drivers/fsi/i2cr-scom.c b/drivers/fsi/i2cr-scom.c index cb7e02213032..dfdb16afd205 100644 --- a/drivers/fsi/i2cr-scom.c +++ b/drivers/fsi/i2cr-scom.c @@ -140,7 +140,6 @@ static struct fsi_driver i2cr_scom_driver = { .id_table = i2cr_scom_ids, .drv = { .name = "i2cr_scom", - .bus = &fsi_bus_type, .of_match_table = i2cr_scom_of_ids, .probe = i2cr_scom_probe, .remove = i2cr_scom_remove, From 18fa479b90c29a48481ee0aae98779278c51b96b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 9 Dec 2025 12:40:27 +0100 Subject: [PATCH 265/297] fsi: Provide thin wrappers around dev_[gs]et_data() for fsi devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Similar to wrappers for other subsystems provide inline functions for fsi devices to store driver data. Signed-off-by: Uwe Kleine-König Acked-by: Eddie James Link: https://patch.msgid.link/5de7a7cbb30918b3503235130bd8aa1a9a63d71c.1765279318.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- include/linux/fsi.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/linux/fsi.h b/include/linux/fsi.h index adea1b432f2d..05be75869a69 100644 --- a/include/linux/fsi.h +++ b/include/linux/fsi.h @@ -19,6 +19,16 @@ struct fsi_device { uint32_t size; }; +static inline void *fsi_get_drvdata(struct fsi_device *fsi_dev) +{ + return dev_get_drvdata(&fsi_dev->dev); +} + +static inline void fsi_set_drvdata(struct fsi_device *fsi_dev, void *data) +{ + dev_set_drvdata(&fsi_dev->dev, data); +} + extern int fsi_device_read(struct fsi_device *dev, uint32_t addr, void *val, size_t size); extern int fsi_device_write(struct fsi_device *dev, uint32_t addr, From 76497839ee0b302f923099bc23aea3d1f027913d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 9 Dec 2025 12:40:28 +0100 Subject: [PATCH 266/297] i2c: fsi: Drop assigning fsi bus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit FIXME ("fsi: Assign driver's bus in fsi_driver_register()") module_fsi_driver() cares about assigning the driver's bus member. Drop the explicit driver specific assignment. Signed-off-by: Uwe Kleine-König Reviewed-by: Andi Shyti Acked-by: Eddie James Link: https://patch.msgid.link/8a36a0dba809d3bfe225627fd178daece10ff6a5.1765279318.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/i2c/busses/i2c-fsi.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-fsi.c b/drivers/i2c/busses/i2c-fsi.c index ae016a9431da..e98dd5dcac0f 100644 --- a/drivers/i2c/busses/i2c-fsi.c +++ b/drivers/i2c/busses/i2c-fsi.c @@ -763,7 +763,6 @@ static struct fsi_driver fsi_i2c_driver = { .id_table = fsi_i2c_ids, .drv = { .name = "i2c-fsi", - .bus = &fsi_bus_type, .probe = fsi_i2c_probe, .remove = fsi_i2c_remove, }, From 605fdbaec14d3b95b1ad3a3289ced24bad0f4b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 9 Dec 2025 12:40:29 +0100 Subject: [PATCH 267/297] spi: fsi: Drop assigning fsi bus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit FIXME ("fsi: Assign driver's bus in fsi_driver_register()") module_fsi_driver() cares about assigning the driver's bus member. Drop the explicit driver specific assignment. Signed-off-by: Uwe Kleine-König Acked-by: Mark Brown Acked-by: Eddie James Link: https://patch.msgid.link/279e26a6740f10d119be7ea01d4af596309b3bb4.1765279318.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/spi/spi-fsi.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/spi/spi-fsi.c b/drivers/spi/spi-fsi.c index e01c63d23b64..f9c15b99dba5 100644 --- a/drivers/spi/spi-fsi.c +++ b/drivers/spi/spi-fsi.c @@ -595,7 +595,6 @@ static struct fsi_driver fsi_spi_driver = { .id_table = fsi_spi_ids, .drv = { .name = "spi-fsi", - .bus = &fsi_bus_type, .probe = fsi_spi_probe, }, }; From 68cc6588b52359ff9b48516525b463551a4bb315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 9 Dec 2025 12:40:30 +0100 Subject: [PATCH 268/297] fsi: Make fsi_bus_type a private variable to the core MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are no users of fsi_bus_type outside of fsi-core.c, so make that variable static, don't export it and drop the declaration from the public header file. As there is a usage of fsi_bus_type in fsi_create_device() the definition of that variable must happen further up in the file to not have to add a local declaration. Signed-off-by: Uwe Kleine-König Acked-by: Eddie James Link: https://patch.msgid.link/bfd83034dec04d5a6b01a234988377fc6224614d.1765279318.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/fsi/fsi-core.c | 67 +++++++++++++++++++++--------------------- include/linux/fsi.h | 1 - 2 files changed, 33 insertions(+), 35 deletions(-) diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c index e1ea1124282e..4e60d4b17c11 100644 --- a/drivers/fsi/fsi-core.c +++ b/drivers/fsi/fsi-core.c @@ -100,6 +100,39 @@ static int fsi_master_write(struct fsi_master *master, int link, uint8_t slave_id, uint32_t addr, const void *val, size_t size); static int fsi_master_break(struct fsi_master *master, int link); +/* FSI core & Linux bus type definitions */ + +static int fsi_bus_match(struct device *dev, const struct device_driver *drv) +{ + struct fsi_device *fsi_dev = to_fsi_dev(dev); + const struct fsi_driver *fsi_drv = to_fsi_drv(drv); + const struct fsi_device_id *id; + + if (!fsi_drv->id_table) + return 0; + + for (id = fsi_drv->id_table; id->engine_type; id++) { + if (id->engine_type != fsi_dev->engine_type) + continue; + if (id->version == FSI_VERSION_ANY || + id->version == fsi_dev->version) { + if (drv->of_match_table) { + if (of_driver_match_device(dev, drv)) + return 1; + } else { + return 1; + } + } + } + + return 0; +} + +static const struct bus_type fsi_bus_type = { + .name = "fsi", + .match = fsi_bus_match, +}; + /* * fsi_device_read() / fsi_device_write() / fsi_device_peek() * @@ -1359,34 +1392,6 @@ void fsi_master_unregister(struct fsi_master *master) } EXPORT_SYMBOL_GPL(fsi_master_unregister); -/* FSI core & Linux bus type definitions */ - -static int fsi_bus_match(struct device *dev, const struct device_driver *drv) -{ - struct fsi_device *fsi_dev = to_fsi_dev(dev); - const struct fsi_driver *fsi_drv = to_fsi_drv(drv); - const struct fsi_device_id *id; - - if (!fsi_drv->id_table) - return 0; - - for (id = fsi_drv->id_table; id->engine_type; id++) { - if (id->engine_type != fsi_dev->engine_type) - continue; - if (id->version == FSI_VERSION_ANY || - id->version == fsi_dev->version) { - if (drv->of_match_table) { - if (of_driver_match_device(dev, drv)) - return 1; - } else { - return 1; - } - } - } - - return 0; -} - int fsi_driver_register(struct fsi_driver *fsi_drv) { if (!fsi_drv) @@ -1406,12 +1411,6 @@ void fsi_driver_unregister(struct fsi_driver *fsi_drv) } EXPORT_SYMBOL_GPL(fsi_driver_unregister); -const struct bus_type fsi_bus_type = { - .name = "fsi", - .match = fsi_bus_match, -}; -EXPORT_SYMBOL_GPL(fsi_bus_type); - static int __init fsi_init(void) { int rc; diff --git a/include/linux/fsi.h b/include/linux/fsi.h index 05be75869a69..3e3a8f3adac3 100644 --- a/include/linux/fsi.h +++ b/include/linux/fsi.h @@ -78,7 +78,6 @@ extern int fsi_slave_read(struct fsi_slave *slave, uint32_t addr, extern int fsi_slave_write(struct fsi_slave *slave, uint32_t addr, const void *val, size_t size); -extern const struct bus_type fsi_bus_type; extern const struct device_type fsi_cdev_type; enum fsi_dev_type { From 69d4ca009005c14068fe358196c38281ec5ee316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 9 Dec 2025 12:40:31 +0100 Subject: [PATCH 269/297] fsi: Create bus specific probe and remove functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce a bus specific probe and remove function. For now this only allows to get rid of a cast of the generic device to an fsi device in the drivers and changes the remove prototype to return void---a non-zero return value is ignored anyhow. The objective is to get rid of users of struct device callbacks .probe(), .remove() and .shutdown() to eventually remove these. Until all fsi drivers are converted this results in a runtime warning about the drivers needing an update because there is a bus probe function and a driver probe function. Signed-off-by: Uwe Kleine-König Acked-by: Eddie James Link: https://patch.msgid.link/3b53adb75a5ae7894736d46cb6eb85f5ef36520e.1765279318.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/fsi/fsi-core.c | 50 ++++++++++++++++++++++++++++++++++++++++++ include/linux/fsi.h | 2 ++ 2 files changed, 52 insertions(+) diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c index 4e60d4b17c11..83599a1c548b 100644 --- a/drivers/fsi/fsi-core.c +++ b/drivers/fsi/fsi-core.c @@ -128,9 +128,31 @@ static int fsi_bus_match(struct device *dev, const struct device_driver *drv) return 0; } +static int fsi_probe(struct device *dev) +{ + struct fsi_device *fsidev = to_fsi_dev(dev); + struct fsi_driver *fsidrv = to_fsi_drv(dev->driver); + + if (fsidrv->probe) + return fsidrv->probe(fsidev); + else + return 0; +} + +static void fsi_remove(struct device *dev) +{ + struct fsi_device *fsidev = to_fsi_dev(dev); + struct fsi_driver *fsidrv = to_fsi_drv(dev->driver); + + if (fsidrv->remove) + fsidrv->remove(fsidev); +} + static const struct bus_type fsi_bus_type = { .name = "fsi", .match = fsi_bus_match, + .probe = fsi_probe, + .remove = fsi_remove, }; /* @@ -1392,6 +1414,25 @@ void fsi_master_unregister(struct fsi_master *master) } EXPORT_SYMBOL_GPL(fsi_master_unregister); +static int fsi_legacy_probe(struct fsi_device *fsidev) +{ + struct device *dev = &fsidev->dev; + struct device_driver *driver = dev->driver; + + return driver->probe(dev); +} + +static void fsi_legacy_remove(struct fsi_device *fsidev) +{ + struct device *dev = &fsidev->dev; + struct device_driver *driver = dev->driver; + int ret; + + ret = driver->remove(dev); + if (unlikely(ret)) + dev_warn(dev, "Ignoring return value of remove callback (%pe)\n", ERR_PTR(ret)); +} + int fsi_driver_register(struct fsi_driver *fsi_drv) { if (!fsi_drv) @@ -1401,6 +1442,15 @@ int fsi_driver_register(struct fsi_driver *fsi_drv) fsi_drv->drv.bus = &fsi_bus_type; + /* + * This driver needs updating. Note that driver_register() warns about + * this, so we're not adding another warning here. + */ + if (!fsi_drv->probe && fsi_drv->drv.probe) + fsi_drv->probe = fsi_legacy_probe; + if (!fsi_drv->remove && fsi_drv->drv.remove) + fsi_drv->remove = fsi_legacy_remove; + return driver_register(&fsi_drv->drv); } EXPORT_SYMBOL_GPL(fsi_driver_register); diff --git a/include/linux/fsi.h b/include/linux/fsi.h index 3e3a8f3adac3..9c67c43f9e6c 100644 --- a/include/linux/fsi.h +++ b/include/linux/fsi.h @@ -49,6 +49,8 @@ struct fsi_device_id { .engine_type = (t), .version = (v), struct fsi_driver { + int (*probe)(struct fsi_device *fsidev); + void (*remove)(struct fsi_device *fsidev); struct device_driver drv; const struct fsi_device_id *id_table; }; From ec93d2ea3d777d20951ac29851342e096e8d4079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 9 Dec 2025 12:40:32 +0100 Subject: [PATCH 270/297] fsi: i2cr-scom: Convert to fsi bus probe mechanism MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fsi bus got a dedicated probe and remove callback. Make use of them. This fixes a runtime warning about the driver needing to be converted to the bus methods. Signed-off-by: Uwe Kleine-König Acked-by: Eddie James Link: https://patch.msgid.link/694a7d077316c3e2883a8410eade67cd578ee556.1765279318.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/fsi/i2cr-scom.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/fsi/i2cr-scom.c b/drivers/fsi/i2cr-scom.c index dfdb16afd205..3efca2e944bb 100644 --- a/drivers/fsi/i2cr-scom.c +++ b/drivers/fsi/i2cr-scom.c @@ -81,9 +81,9 @@ static const struct file_operations i2cr_scom_fops = { .write = i2cr_scom_write, }; -static int i2cr_scom_probe(struct device *dev) +static int i2cr_scom_probe(struct fsi_device *fsi_dev) { - struct fsi_device *fsi_dev = to_fsi_dev(dev); + struct device *dev = &fsi_dev->dev; struct i2cr_scom *scom; int didx; int ret; @@ -115,14 +115,12 @@ static int i2cr_scom_probe(struct device *dev) return ret; } -static int i2cr_scom_remove(struct device *dev) +static void i2cr_scom_remove(struct fsi_device *fsi_dev) { - struct i2cr_scom *scom = dev_get_drvdata(dev); + struct i2cr_scom *scom = dev_get_drvdata(&fsi_dev->dev); cdev_device_del(&scom->cdev, &scom->dev); fsi_free_minor(scom->dev.devt); - - return 0; } static const struct of_device_id i2cr_scom_of_ids[] = { @@ -137,12 +135,12 @@ static const struct fsi_device_id i2cr_scom_ids[] = { }; static struct fsi_driver i2cr_scom_driver = { + .probe = i2cr_scom_probe, + .remove = i2cr_scom_remove, .id_table = i2cr_scom_ids, .drv = { .name = "i2cr_scom", .of_match_table = i2cr_scom_of_ids, - .probe = i2cr_scom_probe, - .remove = i2cr_scom_remove, } }; From 573e29c501684e2ecb0bf0554fc7e502c654d04f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 9 Dec 2025 12:40:33 +0100 Subject: [PATCH 271/297] fsi: master: Convert to fsi bus probe mechanism MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fsi bus got a dedicated probe function. Make use of that. This fixes a runtime warning about the driver needing to be converted to the bus probe method. Signed-off-by: Uwe Kleine-König Acked-by: Eddie James Link: https://patch.msgid.link/17686d71b4ad3f7ebb63e92453273095a5dd09ea.1765279318.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/fsi/fsi-master-hub.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/fsi/fsi-master-hub.c b/drivers/fsi/fsi-master-hub.c index d389856d18ac..a84955bb23b1 100644 --- a/drivers/fsi/fsi-master-hub.c +++ b/drivers/fsi/fsi-master-hub.c @@ -192,9 +192,9 @@ static int hub_master_init(struct fsi_master_hub *hub) return fsi_device_write(dev, FSI_MRESB0, ®, sizeof(reg)); } -static int hub_master_probe(struct device *dev) +static int hub_master_probe(struct fsi_device *fsi_dev) { - struct fsi_device *fsi_dev = to_fsi_dev(dev); + struct device *dev = &fsi_dev->dev; struct fsi_master_hub *hub; uint32_t reg, links; __be32 __reg; @@ -235,7 +235,7 @@ static int hub_master_probe(struct device *dev) hub->master.send_break = hub_master_break; hub->master.link_enable = hub_master_link_enable; - dev_set_drvdata(dev, hub); + fsi_set_drvdata(fsi_dev, hub); hub_master_init(hub); @@ -259,9 +259,9 @@ err_release: return rc; } -static int hub_master_remove(struct device *dev) +static void hub_master_remove(struct fsi_device *fsi_dev) { - struct fsi_master_hub *hub = dev_get_drvdata(dev); + struct fsi_master_hub *hub = fsi_get_drvdata(fsi_dev); fsi_master_unregister(&hub->master); fsi_slave_release_range(hub->upstream->slave, hub->addr, hub->size); @@ -272,8 +272,6 @@ static int hub_master_remove(struct device *dev) * the hub */ put_device(&hub->master.dev); - - return 0; } static const struct fsi_device_id hub_master_ids[] = { @@ -286,10 +284,10 @@ static const struct fsi_device_id hub_master_ids[] = { static struct fsi_driver hub_master_driver = { .id_table = hub_master_ids, + .probe = hub_master_probe, + .remove = hub_master_remove, .drv = { .name = "fsi-master-hub", - .probe = hub_master_probe, - .remove = hub_master_remove, } }; From 0cf9580a3ed8ccca9590bda8a173685e73c037af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 9 Dec 2025 12:40:34 +0100 Subject: [PATCH 272/297] fsi: sbefifo: Convert to fsi bus probe mechanism MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fsi bus got a dedicated probe function. Make use of that. This fixes a runtime warning about the driver needing to be converted to the bus probe method. Signed-off-by: Uwe Kleine-König Acked-by: Eddie James Link: https://patch.msgid.link/79dd5a9459f0719b7602165c89eb6fc24815f3b5.1765279318.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/fsi/fsi-sbefifo.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c index fde1c34743a0..6ca5817910cd 100644 --- a/drivers/fsi/fsi-sbefifo.c +++ b/drivers/fsi/fsi-sbefifo.c @@ -1022,9 +1022,9 @@ static void sbefifo_free(struct device *dev) * Probe/remove */ -static int sbefifo_probe(struct device *dev) +static int sbefifo_probe(struct fsi_device *fsi_dev) { - struct fsi_device *fsi_dev = to_fsi_dev(dev); + struct device *dev = &fsi_dev->dev; struct sbefifo *sbefifo; struct device_node *np; struct platform_device *child; @@ -1045,7 +1045,7 @@ static int sbefifo_probe(struct device *dev) sbefifo->magic = SBEFIFO_MAGIC; sbefifo->fsi_dev = fsi_dev; - dev_set_drvdata(dev, sbefifo); + fsi_set_drvdata(fsi_dev, sbefifo); mutex_init(&sbefifo->lock); sbefifo->timeout_in_cmd_ms = SBEFIFO_TIMEOUT_IN_CMD; sbefifo->timeout_start_rsp_ms = SBEFIFO_TIMEOUT_START_RSP; @@ -1101,9 +1101,10 @@ static int sbefifo_unregister_child(struct device *dev, void *data) return 0; } -static int sbefifo_remove(struct device *dev) +static void sbefifo_remove(struct fsi_device *fsi_dev) { - struct sbefifo *sbefifo = dev_get_drvdata(dev); + struct device *dev = &fsi_dev->dev; + struct sbefifo *sbefifo = fsi_get_drvdata(fsi_dev); dev_dbg(dev, "Removing sbefifo device...\n"); @@ -1117,8 +1118,6 @@ static int sbefifo_remove(struct device *dev) fsi_free_minor(sbefifo->dev.devt); device_for_each_child(dev, NULL, sbefifo_unregister_child); put_device(&sbefifo->dev); - - return 0; } static const struct fsi_device_id sbefifo_ids[] = { @@ -1131,10 +1130,10 @@ static const struct fsi_device_id sbefifo_ids[] = { static struct fsi_driver sbefifo_drv = { .id_table = sbefifo_ids, + .probe = sbefifo_probe, + .remove = sbefifo_remove, .drv = { .name = DEVICE_NAME, - .probe = sbefifo_probe, - .remove = sbefifo_remove, } }; From 0d295b19bd1e85b6a4e0b139125f37a76ce307cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 9 Dec 2025 12:40:35 +0100 Subject: [PATCH 273/297] fsi: scom: Convert to fsi bus probe mechanism MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fsi bus got a dedicated probe function. Make use of that. This fixes a runtime warning about the driver needing to be converted to the bus probe method. Signed-off-by: Uwe Kleine-König Acked-by: Eddie James Link: https://patch.msgid.link/d02a22f2f5442dc03d1d803d1b8190bc89bc71d4.1765279318.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/fsi/fsi-scom.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/fsi/fsi-scom.c b/drivers/fsi/fsi-scom.c index 2eda44451cc1..67cd45605fe4 100644 --- a/drivers/fsi/fsi-scom.c +++ b/drivers/fsi/fsi-scom.c @@ -527,16 +527,16 @@ static void scom_free(struct device *dev) kfree(scom); } -static int scom_probe(struct device *dev) +static int scom_probe(struct fsi_device *fsi_dev) { - struct fsi_device *fsi_dev = to_fsi_dev(dev); + struct device *dev = &fsi_dev->dev; struct scom_device *scom; int rc, didx; scom = kzalloc(sizeof(*scom), GFP_KERNEL); if (!scom) return -ENOMEM; - dev_set_drvdata(dev, scom); + fsi_set_drvdata(fsi_dev, scom); mutex_init(&scom->lock); /* Grab a reference to the device (parent of our cdev), we'll drop it later */ @@ -574,9 +574,9 @@ static int scom_probe(struct device *dev) return rc; } -static int scom_remove(struct device *dev) +static void scom_remove(struct fsi_device *fsi_dev) { - struct scom_device *scom = dev_get_drvdata(dev); + struct scom_device *scom = fsi_get_drvdata(fsi_dev); mutex_lock(&scom->lock); scom->dead = true; @@ -584,8 +584,6 @@ static int scom_remove(struct device *dev) cdev_device_del(&scom->cdev, &scom->dev); fsi_free_minor(scom->dev.devt); put_device(&scom->dev); - - return 0; } static const struct of_device_id scom_of_ids[] = { @@ -604,11 +602,11 @@ static const struct fsi_device_id scom_ids[] = { static struct fsi_driver scom_drv = { .id_table = scom_ids, + .probe = scom_probe, + .remove = scom_remove, .drv = { .name = "scom", .of_match_table = scom_of_ids, - .probe = scom_probe, - .remove = scom_remove, } }; From 1086210a464c8775afa310209006ddc63f8ecb85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 9 Dec 2025 12:40:36 +0100 Subject: [PATCH 274/297] i2c: fsi: Convert to fsi bus probe mechanism MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fsi bus got a dedicated probe function. Make use of that. This fixes a runtime warning about the driver needing to be converted to the bus probe method. Signed-off-by: Uwe Kleine-König Reviewed-by: Andi Shyti Acked-by: Eddie James Link: https://patch.msgid.link/d8c27aed45bf3119c08c9772768d675ae2ccc0c3.1765279318.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/i2c/busses/i2c-fsi.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/i2c/busses/i2c-fsi.c b/drivers/i2c/busses/i2c-fsi.c index e98dd5dcac0f..3a7e577e6eac 100644 --- a/drivers/i2c/busses/i2c-fsi.c +++ b/drivers/i2c/busses/i2c-fsi.c @@ -674,8 +674,9 @@ static struct device_node *fsi_i2c_find_port_of_node(struct device_node *fsi, return NULL; } -static int fsi_i2c_probe(struct device *dev) +static int fsi_i2c_probe(struct fsi_device *fsi_dev) { + struct device *dev = &fsi_dev->dev; struct fsi_i2c_ctrl *i2c; struct fsi_i2c_port *port; struct device_node *np; @@ -735,14 +736,14 @@ static int fsi_i2c_probe(struct device *dev) list_add(&port->list, &i2c->ports); } - dev_set_drvdata(dev, i2c); + fsi_set_drvdata(fsi_dev, i2c); return 0; } -static int fsi_i2c_remove(struct device *dev) +static void fsi_i2c_remove(struct fsi_device *fsi_dev) { - struct fsi_i2c_ctrl *i2c = dev_get_drvdata(dev); + struct fsi_i2c_ctrl *i2c = fsi_get_drvdata(fsi_dev); struct fsi_i2c_port *port, *tmp; list_for_each_entry_safe(port, tmp, &i2c->ports, list) { @@ -750,8 +751,6 @@ static int fsi_i2c_remove(struct device *dev) i2c_del_adapter(&port->adapter); kfree(port); } - - return 0; } static const struct fsi_device_id fsi_i2c_ids[] = { @@ -761,10 +760,10 @@ static const struct fsi_device_id fsi_i2c_ids[] = { static struct fsi_driver fsi_i2c_driver = { .id_table = fsi_i2c_ids, + .probe = fsi_i2c_probe, + .remove = fsi_i2c_remove, .drv = { .name = "i2c-fsi", - .probe = fsi_i2c_probe, - .remove = fsi_i2c_remove, }, }; From 370fdb9825f3650f05f2eed735a9086b1090920c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Tue, 9 Dec 2025 12:40:37 +0100 Subject: [PATCH 275/297] spi: fsi: Convert to fsi bus probe mechanism MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fsi bus got a dedicated probe function. Make use of that. This fixes a runtime warning about the driver needing to be converted to the bus probe method. Signed-off-by: Uwe Kleine-König Acked-by: Mark Brown Acked-by: Eddie James Link: https://patch.msgid.link/fc2a758ef00844dd5bd614a25b36a4a38355d12d.1765279318.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/spi/spi-fsi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/spi/spi-fsi.c b/drivers/spi/spi-fsi.c index f9c15b99dba5..07dc3d24f2c9 100644 --- a/drivers/spi/spi-fsi.c +++ b/drivers/spi/spi-fsi.c @@ -528,13 +528,13 @@ static size_t fsi_spi_max_transfer_size(struct spi_device *spi) return SPI_FSI_MAX_RX_SIZE; } -static int fsi_spi_probe(struct device *dev) +static int fsi_spi_probe(struct fsi_device *fsi) { int rc; struct device_node *np; int num_controllers_registered = 0; struct fsi2spi *bridge; - struct fsi_device *fsi = to_fsi_dev(dev); + struct device *dev = &fsi->dev; rc = fsi_spi_check_mux(fsi, dev); if (rc) @@ -593,9 +593,9 @@ MODULE_DEVICE_TABLE(fsi, fsi_spi_ids); static struct fsi_driver fsi_spi_driver = { .id_table = fsi_spi_ids, + .probe = fsi_spi_probe, .drv = { .name = "spi-fsi", - .probe = fsi_spi_probe, }, }; module_fsi_driver(fsi_spi_driver); From 04d390af97f2c28166f7ddfe1a6bda622e3a4766 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Wed, 28 Jan 2026 10:55:36 +0100 Subject: [PATCH 276/297] iio: Use IRQF_NO_THREAD The interrupt handler iio_trigger_generic_data_rdy_poll() will invoke other interrupt handler and this supposed to happen from within the hardirq. Use IRQF_NO_THREAD to forbid forced-threading. Signed-off-by: Sebastian Andrzej Siewior Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma180.c | 5 +++-- drivers/iio/adc/ad7766.c | 2 +- drivers/iio/gyro/itg3200_buffer.c | 8 +++----- drivers/iio/light/si1145.c | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/drivers/iio/accel/bma180.c b/drivers/iio/accel/bma180.c index 8925f5279e62..7bc6761f5135 100644 --- a/drivers/iio/accel/bma180.c +++ b/drivers/iio/accel/bma180.c @@ -986,8 +986,9 @@ static int bma180_probe(struct i2c_client *client) } ret = devm_request_irq(dev, client->irq, - iio_trigger_generic_data_rdy_poll, IRQF_TRIGGER_RISING, - "bma180_event", data->trig); + iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, + "bma180_event", data->trig); if (ret) { dev_err(dev, "unable to request IRQ\n"); goto err_trigger_free; diff --git a/drivers/iio/adc/ad7766.c b/drivers/iio/adc/ad7766.c index 4d570383ef02..1e6bfe8765ab 100644 --- a/drivers/iio/adc/ad7766.c +++ b/drivers/iio/adc/ad7766.c @@ -261,7 +261,7 @@ static int ad7766_probe(struct spi_device *spi) * don't enable the interrupt to avoid extra load on the system */ ret = devm_request_irq(&spi->dev, spi->irq, ad7766_irq, - IRQF_TRIGGER_FALLING | IRQF_NO_AUTOEN, + IRQF_TRIGGER_FALLING | IRQF_NO_AUTOEN | IRQF_NO_THREAD, dev_name(&spi->dev), ad7766->trig); if (ret < 0) diff --git a/drivers/iio/gyro/itg3200_buffer.c b/drivers/iio/gyro/itg3200_buffer.c index a624400a239c..cf97adfa9727 100644 --- a/drivers/iio/gyro/itg3200_buffer.c +++ b/drivers/iio/gyro/itg3200_buffer.c @@ -118,11 +118,9 @@ int itg3200_probe_trigger(struct iio_dev *indio_dev) if (!st->trig) return -ENOMEM; - ret = request_irq(st->i2c->irq, - &iio_trigger_generic_data_rdy_poll, - IRQF_TRIGGER_RISING, - "itg3200_data_rdy", - st->trig); + ret = request_irq(st->i2c->irq, &iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, + "itg3200_data_rdy", st->trig); if (ret) goto error_free_trig; diff --git a/drivers/iio/light/si1145.c b/drivers/iio/light/si1145.c index f8eb251eca8d..ef0abc4499b7 100644 --- a/drivers/iio/light/si1145.c +++ b/drivers/iio/light/si1145.c @@ -1248,7 +1248,7 @@ static int si1145_probe_trigger(struct iio_dev *indio_dev) ret = devm_request_irq(&client->dev, client->irq, iio_trigger_generic_data_rdy_poll, - IRQF_TRIGGER_FALLING, + IRQF_TRIGGER_FALLING | IRQF_NO_THREAD, "si1145_irq", trig); if (ret < 0) { From ac9fabd578a03bfa0d599e3fcd9c4e106661e3d2 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Wed, 28 Jan 2026 10:55:37 +0100 Subject: [PATCH 277/297] iio: Replace IRQF_ONESHOT with IRQF_NO_THREAD Passing IRQF_ONESHOT ensures that the interrupt source is masked until the secondary (threaded) handler is done. If only a primary handler is used then the flag makes no sense because the interrupt can not fire (again) while its handler is running. The flag also disallows force-threading of the primary handler and the irq-core will warn about this. The intention here was probably not allowing forced-threading for handlers such as iio_trigger_generic_data_rdy_poll() will intends to invoke hard-interrupt handlers. Replace IRQF_ONESHOT with IRQF_NO_THREAD. Reviewed-by: Andy Shevchenko Reviewed-by: Marcus Folkesson Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl355_core.c | 5 ++--- drivers/iio/accel/adxl372.c | 9 ++++----- drivers/iio/accel/mxc4005.c | 11 ++++------- drivers/iio/accel/stk8ba50.c | 11 ++++------- drivers/iio/adc/ad4170-4.c | 2 +- drivers/iio/adc/ad7768-1.c | 5 ++--- drivers/iio/adc/ad7779.c | 2 +- drivers/iio/adc/mcp3911.c | 2 +- drivers/iio/adc/ti-ads131e08.c | 2 +- drivers/iio/chemical/ens160_core.c | 9 +++------ drivers/iio/gyro/adxrs290.c | 2 +- drivers/iio/health/afe4403.c | 9 ++++----- drivers/iio/health/afe4404.c | 9 ++++----- drivers/iio/magnetometer/bmc150_magn.c | 9 +++------ drivers/iio/pressure/dlhl60d.c | 7 +++---- drivers/iio/temperature/tmp006.c | 10 ++++------ 16 files changed, 42 insertions(+), 62 deletions(-) diff --git a/drivers/iio/accel/adxl355_core.c b/drivers/iio/accel/adxl355_core.c index 5fc7f814b907..1c1d64d5cbcb 100644 --- a/drivers/iio/accel/adxl355_core.c +++ b/drivers/iio/accel/adxl355_core.c @@ -768,9 +768,8 @@ static int adxl355_probe_trigger(struct iio_dev *indio_dev, int irq) data->dready_trig->ops = &adxl355_trigger_ops; iio_trigger_set_drvdata(data->dready_trig, indio_dev); - ret = devm_request_irq(data->dev, irq, - &iio_trigger_generic_data_rdy_poll, - IRQF_ONESHOT, "adxl355_irq", data->dready_trig); + ret = devm_request_irq(data->dev, irq, &iio_trigger_generic_data_rdy_poll, + IRQF_NO_THREAD, "adxl355_irq", data->dready_trig); if (ret) return dev_err_probe(data->dev, ret, "request irq %d failed\n", irq); diff --git a/drivers/iio/accel/adxl372.c b/drivers/iio/accel/adxl372.c index 46d518a2a029..2f6aa52aeba5 100644 --- a/drivers/iio/accel/adxl372.c +++ b/drivers/iio/accel/adxl372.c @@ -1247,11 +1247,10 @@ int adxl372_probe(struct device *dev, struct regmap *regmap, indio_dev->trig = iio_trigger_get(st->dready_trig); - ret = devm_request_threaded_irq(dev, st->irq, - iio_trigger_generic_data_rdy_poll, - NULL, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, - indio_dev->name, st->dready_trig); + ret = devm_request_irq(dev, st->irq, + iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, + indio_dev->name, st->dready_trig); if (ret < 0) return ret; } diff --git a/drivers/iio/accel/mxc4005.c b/drivers/iio/accel/mxc4005.c index ac973d871c8b..a2c3cf13d098 100644 --- a/drivers/iio/accel/mxc4005.c +++ b/drivers/iio/accel/mxc4005.c @@ -486,13 +486,10 @@ static int mxc4005_probe(struct i2c_client *client) if (!data->dready_trig) return -ENOMEM; - ret = devm_request_threaded_irq(&client->dev, client->irq, - iio_trigger_generic_data_rdy_poll, - NULL, - IRQF_TRIGGER_FALLING | - IRQF_ONESHOT, - "mxc4005_event", - data->dready_trig); + ret = devm_request_irq(&client->dev, client->irq, + iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_FALLING | IRQF_NO_THREAD, + "mxc4005_event", data->dready_trig); if (ret) { dev_err(&client->dev, "failed to init threaded irq\n"); diff --git a/drivers/iio/accel/stk8ba50.c b/drivers/iio/accel/stk8ba50.c index 384f1fbcbcb3..a9ff2a273fe1 100644 --- a/drivers/iio/accel/stk8ba50.c +++ b/drivers/iio/accel/stk8ba50.c @@ -428,13 +428,10 @@ static int stk8ba50_probe(struct i2c_client *client) } if (client->irq > 0) { - ret = devm_request_threaded_irq(&client->dev, client->irq, - stk8ba50_data_rdy_trig_poll, - NULL, - IRQF_TRIGGER_RISING | - IRQF_ONESHOT, - "stk8ba50_event", - indio_dev); + ret = devm_request_irq(&client->dev, client->irq, + stk8ba50_data_rdy_trig_poll, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, + "stk8ba50_event", indio_dev); if (ret < 0) { dev_err(&client->dev, "request irq %d failed\n", client->irq); diff --git a/drivers/iio/adc/ad4170-4.c b/drivers/iio/adc/ad4170-4.c index efaed92191f1..82205bfae531 100644 --- a/drivers/iio/adc/ad4170-4.c +++ b/drivers/iio/adc/ad4170-4.c @@ -2973,7 +2973,7 @@ static int ad4170_probe(struct spi_device *spi) if (spi->irq) { ret = devm_request_irq(dev, spi->irq, &ad4170_irq_handler, - IRQF_ONESHOT, indio_dev->name, indio_dev); + IRQF_NO_THREAD, indio_dev->name, indio_dev); if (ret) return ret; diff --git a/drivers/iio/adc/ad7768-1.c b/drivers/iio/adc/ad7768-1.c index 980c079ab41a..fcd8aea7152e 100644 --- a/drivers/iio/adc/ad7768-1.c +++ b/drivers/iio/adc/ad7768-1.c @@ -1711,9 +1711,8 @@ static int ad7768_probe(struct spi_device *spi) if (ret) return ret; - ret = devm_request_irq(&spi->dev, spi->irq, - &ad7768_interrupt, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, + ret = devm_request_irq(&spi->dev, spi->irq, &ad7768_interrupt, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, indio_dev->name, indio_dev); if (ret) return ret; diff --git a/drivers/iio/adc/ad7779.c b/drivers/iio/adc/ad7779.c index aac5049c9a07..695cc79e78da 100644 --- a/drivers/iio/adc/ad7779.c +++ b/drivers/iio/adc/ad7779.c @@ -840,7 +840,7 @@ static int ad7779_setup_without_backend(struct ad7779_state *st, struct iio_dev iio_trigger_set_drvdata(st->trig, st); ret = devm_request_irq(dev, st->spi->irq, iio_trigger_generic_data_rdy_poll, - IRQF_ONESHOT | IRQF_NO_AUTOEN, indio_dev->name, + IRQF_NO_THREAD | IRQF_NO_AUTOEN, indio_dev->name, st->trig); if (ret) return dev_err_probe(dev, ret, "request IRQ %d failed\n", diff --git a/drivers/iio/adc/mcp3911.c b/drivers/iio/adc/mcp3911.c index a6f21791c685..ddc3721f3f68 100644 --- a/drivers/iio/adc/mcp3911.c +++ b/drivers/iio/adc/mcp3911.c @@ -815,7 +815,7 @@ static int mcp3911_probe(struct spi_device *spi) * don't enable the interrupt to avoid extra load on the system. */ ret = devm_request_irq(dev, spi->irq, &iio_trigger_generic_data_rdy_poll, - IRQF_NO_AUTOEN | IRQF_ONESHOT, + IRQF_NO_AUTOEN | IRQF_NO_THREAD, indio_dev->name, adc->trig); if (ret) return ret; diff --git a/drivers/iio/adc/ti-ads131e08.c b/drivers/iio/adc/ti-ads131e08.c index c9a20024d6b1..a585621b0bc3 100644 --- a/drivers/iio/adc/ti-ads131e08.c +++ b/drivers/iio/adc/ti-ads131e08.c @@ -827,7 +827,7 @@ static int ads131e08_probe(struct spi_device *spi) if (spi->irq) { ret = devm_request_irq(&spi->dev, spi->irq, ads131e08_interrupt, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + IRQF_TRIGGER_FALLING | IRQF_NO_THREAD, spi->dev.driver->name, indio_dev); if (ret) return dev_err_probe(&spi->dev, ret, diff --git a/drivers/iio/chemical/ens160_core.c b/drivers/iio/chemical/ens160_core.c index 86bde4a91bf7..bbc96c4c6283 100644 --- a/drivers/iio/chemical/ens160_core.c +++ b/drivers/iio/chemical/ens160_core.c @@ -316,12 +316,9 @@ static int ens160_setup_trigger(struct iio_dev *indio_dev, int irq) indio_dev->trig = iio_trigger_get(trig); - ret = devm_request_threaded_irq(dev, irq, - iio_trigger_generic_data_rdy_poll, - NULL, - IRQF_ONESHOT, - indio_dev->name, - indio_dev->trig); + ret = devm_request_irq(dev, irq, iio_trigger_generic_data_rdy_poll, + IRQF_NO_THREAD, indio_dev->name, + indio_dev->trig); if (ret) return dev_err_probe(dev, ret, "failed to request irq\n"); diff --git a/drivers/iio/gyro/adxrs290.c b/drivers/iio/gyro/adxrs290.c index 8fcb41f45baa..3efe385ebedc 100644 --- a/drivers/iio/gyro/adxrs290.c +++ b/drivers/iio/gyro/adxrs290.c @@ -597,7 +597,7 @@ static int adxrs290_probe_trigger(struct iio_dev *indio_dev) ret = devm_request_irq(&st->spi->dev, st->spi->irq, &iio_trigger_generic_data_rdy_poll, - IRQF_ONESHOT, "adxrs290_irq", st->dready_trig); + IRQF_NO_THREAD, "adxrs290_irq", st->dready_trig); if (ret < 0) return dev_err_probe(&st->spi->dev, ret, "request irq %d failed\n", st->spi->irq); diff --git a/drivers/iio/health/afe4403.c b/drivers/iio/health/afe4403.c index 0e5a512e3bb8..d358f4d5e5da 100644 --- a/drivers/iio/health/afe4403.c +++ b/drivers/iio/health/afe4403.c @@ -540,11 +540,10 @@ static int afe4403_probe(struct spi_device *spi) return ret; } - ret = devm_request_threaded_irq(dev, afe->irq, - iio_trigger_generic_data_rdy_poll, - NULL, IRQF_ONESHOT, - AFE4403_DRIVER_NAME, - afe->trig); + ret = devm_request_irq(dev, afe->irq, + iio_trigger_generic_data_rdy_poll, + IRQF_NO_THREAD, AFE4403_DRIVER_NAME, + afe->trig); if (ret) { dev_err(dev, "Unable to request IRQ\n"); return ret; diff --git a/drivers/iio/health/afe4404.c b/drivers/iio/health/afe4404.c index 768d794e574b..032da52a96d0 100644 --- a/drivers/iio/health/afe4404.c +++ b/drivers/iio/health/afe4404.c @@ -547,11 +547,10 @@ static int afe4404_probe(struct i2c_client *client) return ret; } - ret = devm_request_threaded_irq(dev, afe->irq, - iio_trigger_generic_data_rdy_poll, - NULL, IRQF_ONESHOT, - AFE4404_DRIVER_NAME, - afe->trig); + ret = devm_request_irq(dev, afe->irq, + iio_trigger_generic_data_rdy_poll, + IRQF_NO_THREAD, AFE4404_DRIVER_NAME, + afe->trig); if (ret) { dev_err(dev, "Unable to request IRQ\n"); return ret; diff --git a/drivers/iio/magnetometer/bmc150_magn.c b/drivers/iio/magnetometer/bmc150_magn.c index 6a73f6e2f1f0..a022e1805dff 100644 --- a/drivers/iio/magnetometer/bmc150_magn.c +++ b/drivers/iio/magnetometer/bmc150_magn.c @@ -906,12 +906,9 @@ int bmc150_magn_probe(struct device *dev, struct regmap *regmap, goto err_poweroff; } - ret = request_threaded_irq(irq, - iio_trigger_generic_data_rdy_poll, - NULL, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, - "bmc150_magn_event", - data->dready_trig); + ret = request_irq(irq, iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, + "bmc150_magn_event", data->dready_trig); if (ret < 0) { dev_err(dev, "request irq %d failed\n", irq); goto err_trigger_unregister; diff --git a/drivers/iio/pressure/dlhl60d.c b/drivers/iio/pressure/dlhl60d.c index 8bad7162fec6..46feb27fe632 100644 --- a/drivers/iio/pressure/dlhl60d.c +++ b/drivers/iio/pressure/dlhl60d.c @@ -306,10 +306,9 @@ static int dlh_probe(struct i2c_client *client) indio_dev->num_channels = ARRAY_SIZE(dlh_channels); if (client->irq > 0) { - ret = devm_request_threaded_irq(&client->dev, client->irq, - dlh_interrupt, NULL, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, - st->info->name, indio_dev); + ret = devm_request_irq(&client->dev, client->irq, dlh_interrupt, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, + st->info->name, indio_dev); if (ret) { dev_err(&client->dev, "failed to allocate threaded irq"); return ret; diff --git a/drivers/iio/temperature/tmp006.c b/drivers/iio/temperature/tmp006.c index 10bd3f221929..d8d8c8936d17 100644 --- a/drivers/iio/temperature/tmp006.c +++ b/drivers/iio/temperature/tmp006.c @@ -356,12 +356,10 @@ static int tmp006_probe(struct i2c_client *client) indio_dev->trig = iio_trigger_get(data->drdy_trig); - ret = devm_request_threaded_irq(&client->dev, client->irq, - iio_trigger_generic_data_rdy_poll, - NULL, - IRQF_ONESHOT, - "tmp006_irq", - data->drdy_trig); + ret = devm_request_irq(&client->dev, client->irq, + iio_trigger_generic_data_rdy_poll, + IRQF_NO_THREAD, "tmp006_irq", + data->drdy_trig); if (ret < 0) return ret; } From a54e9440925e6617c98669066b4753c4cdcea8a0 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Wed, 28 Jan 2026 10:55:38 +0100 Subject: [PATCH 278/297] iio: magnetometer: Remove IRQF_ONESHOT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Passing IRQF_ONESHOT ensures that the interrupt source is masked until the secondary (threaded) handler is done. If only a primary handler is used then the flag makes no sense because the interrupt can not fire (again) while its handler is running. The flag also disallows force-threading of the primary handler and the irq-core will warn about this. The force-threading functionality is required on PREEMPT_RT because the handler is using locks with can sleep on PREEMPT_RT. Remove IRQF_ONESHOT from irqflags. Tested-by: Geert Uytterhoeven Signed-off-by: Sebastian Andrzej Siewior Reviewed-by: Andy Shevchenko Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/magnetometer/ak8975.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/magnetometer/ak8975.c b/drivers/iio/magnetometer/ak8975.c index 3fd0171e5d69..d30315ad85de 100644 --- a/drivers/iio/magnetometer/ak8975.c +++ b/drivers/iio/magnetometer/ak8975.c @@ -581,7 +581,7 @@ static int ak8975_setup_irq(struct ak8975_data *data) irq = gpiod_to_irq(data->eoc_gpiod); rc = devm_request_irq(&client->dev, irq, ak8975_irq_handler, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, + IRQF_TRIGGER_RISING, dev_name(&client->dev), data); if (rc < 0) { dev_err(&client->dev, "irq %d request failed: %d\n", irq, rc); From d07b24cc62abc63f47ff30d5d662c3b875797072 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Wed, 28 Jan 2026 10:55:39 +0100 Subject: [PATCH 279/297] iio: adc: ad7766: Use iio_trigger_generic_data_rdy_poll() ad7766_irq() is identical to iio_trigger_generic_data_rdy_poll(). Use iio_trigger_generic_data_rdy_poll() instead of ad7766_irq(). Signed-off-by: Sebastian Andrzej Siewior Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7766.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/iio/adc/ad7766.c b/drivers/iio/adc/ad7766.c index 1e6bfe8765ab..9e4a66477d2d 100644 --- a/drivers/iio/adc/ad7766.c +++ b/drivers/iio/adc/ad7766.c @@ -184,12 +184,6 @@ static const struct iio_info ad7766_info = { .read_raw = &ad7766_read_raw, }; -static irqreturn_t ad7766_irq(int irq, void *private) -{ - iio_trigger_poll(private); - return IRQ_HANDLED; -} - static int ad7766_set_trigger_state(struct iio_trigger *trig, bool enable) { struct ad7766 *ad7766 = iio_trigger_get_drvdata(trig); @@ -260,7 +254,7 @@ static int ad7766_probe(struct spi_device *spi) * Some platforms might not allow the option to power it down so * don't enable the interrupt to avoid extra load on the system */ - ret = devm_request_irq(&spi->dev, spi->irq, ad7766_irq, + ret = devm_request_irq(&spi->dev, spi->irq, iio_trigger_generic_data_rdy_poll, IRQF_TRIGGER_FALLING | IRQF_NO_AUTOEN | IRQF_NO_THREAD, dev_name(&spi->dev), ad7766->trig); From 3ec0a13ae21c68a34481dc2b128a9c064e26d22d Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Thu, 29 Jan 2026 16:11:57 +0200 Subject: [PATCH 280/297] iio: accel: adxl372: remove unused int2_bitmask field Remove unused int2_bitmask field from adxl372_state struct. The field is declared but never accessed in the driver. Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl372.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/iio/accel/adxl372.c b/drivers/iio/accel/adxl372.c index 2f6aa52aeba5..28a8793a53b6 100644 --- a/drivers/iio/accel/adxl372.c +++ b/drivers/iio/accel/adxl372.c @@ -295,7 +295,6 @@ struct adxl372_state { u32 inact_time_ms; u8 fifo_set_size; unsigned long int1_bitmask; - unsigned long int2_bitmask; u16 watermark; __be16 fifo_buf[ADXL372_FIFO_SIZE]; bool peak_fifo_mode_en; From 3e55c3dc93fdbb1cc2ff436fa10996468288c28c Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Thu, 29 Jan 2026 16:11:59 +0200 Subject: [PATCH 281/297] iio: accel: sca3000: remove unused last_timestamp field Remove unused last_timestamp field from sca3000_state struct. The field is declared but never accessed in the driver. Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- drivers/iio/accel/sca3000.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/iio/accel/sca3000.c b/drivers/iio/accel/sca3000.c index bfa8a3f5a92f..950b4c672484 100644 --- a/drivers/iio/accel/sca3000.c +++ b/drivers/iio/accel/sca3000.c @@ -153,7 +153,6 @@ * struct sca3000_state - device instance state information * @us: the associated spi device * @info: chip variant information - * @last_timestamp: the timestamp of the last event * @mo_det_use_count: reference counter for the motion detection unit * @lock: lock used to protect elements of sca3000_state * and the underlying device state. @@ -163,7 +162,6 @@ struct sca3000_state { struct spi_device *us; const struct sca3000_chip_info *info; - s64 last_timestamp; int mo_det_use_count; struct mutex lock; /* Can these share a cacheline ? */ From 53f0a73f9ec7575d4e59dc8dc70e891979ee2014 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Thu, 29 Jan 2026 16:43:07 +0200 Subject: [PATCH 282/297] MAINTAINERS: add entry for ADE9000 driver Add maintainer entry for the ADE9000 polyphase energy metering IC driver, including devicetree bindings and driver documentation. Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 70163f939602..1251965d70bd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1626,6 +1626,14 @@ W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/amplifiers/adi,ada4250.yaml F: drivers/iio/amplifiers/ada4250.c +ANALOG DEVICES INC ADE9000 DRIVER +M: Antoniu Miclaus +L: linux-iio@vger.kernel.org +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml +F: drivers/iio/adc/ade9000.c + ANALOG DEVICES INC ADF4377 DRIVER M: Antoniu Miclaus L: linux-iio@vger.kernel.org From b79b24f578cdb2d657db23e5fafe82c7e6a36b72 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Thu, 29 Jan 2026 17:01:45 +0200 Subject: [PATCH 283/297] iio: gyro: itg3200: Fix unchecked return value in read_raw The return value from itg3200_read_reg_s16() is stored in ret but never checked. The function unconditionally returns IIO_VAL_INT, ignoring potential I2C read failures. This causes garbage data to be returned to userspace when the read fails, with no error reported. Add proper error checking to propagate the failure to callers. Fixes: 9dbf091da080 ("iio: gyro: Add itg3200") Signed-off-by: Antoniu Miclaus Reviewed-by: Andy Shevchenko Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/gyro/itg3200_core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/gyro/itg3200_core.c b/drivers/iio/gyro/itg3200_core.c index cd8a2dae56cd..bfe95ec1abda 100644 --- a/drivers/iio/gyro/itg3200_core.c +++ b/drivers/iio/gyro/itg3200_core.c @@ -93,6 +93,8 @@ static int itg3200_read_raw(struct iio_dev *indio_dev, case IIO_CHAN_INFO_RAW: reg = (u8)chan->address; ret = itg3200_read_reg_s16(indio_dev, reg, val); + if (ret) + return ret; return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: *val = 0; From bf870c97ce2b9b48f02184d317dab4cf5691ca0e Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 28 Jan 2026 12:07:20 -0800 Subject: [PATCH 284/297] iio: buffer: buffer_impl.h: fix kernel-doc warnings Resolve all kernel-doc warnings in buffer_impl.h: Warning: include/linux/iio/buffer_impl.h:172 struct member 'direction' not described in 'iio_buffer' Warning: include/linux/iio/buffer_impl.h:184 No description found for return value of 'iio_update_buffers' Also correct one typo (word order switch) and remove one stray space in a kernel-doc comment. Signed-off-by: Randy Dunlap Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- include/linux/iio/buffer_impl.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/linux/iio/buffer_impl.h b/include/linux/iio/buffer_impl.h index c0b0e0992a85..9442c165561a 100644 --- a/include/linux/iio/buffer_impl.h +++ b/include/linux/iio/buffer_impl.h @@ -113,10 +113,10 @@ struct iio_buffer { /** @flags: File ops flags including busy flag. */ unsigned long flags; - /** @bytes_per_datum: Size of individual datum including timestamp. */ + /** @bytes_per_datum: Size of individual datum including timestamp. */ size_t bytes_per_datum; - /* @direction: Direction of the data stream (in/out). */ + /** @direction: Direction of the data stream (in/out). */ enum iio_buffer_direction direction; /** @@ -178,7 +178,9 @@ struct iio_buffer { * @insert_buffer: buffer to insert * @remove_buffer: buffer_to_remove * - * Note this will tear down the all buffering and build it up again + * Note this will tear down all the buffering and build it up again + * + * Returns: 0 on success or -errno on error */ int iio_update_buffers(struct iio_dev *indio_dev, struct iio_buffer *insert_buffer, From 2693ca2e02793fde06ff9d64175aed6f144f2287 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Tue, 27 Jan 2026 22:25:37 -0800 Subject: [PATCH 285/297] iio: frequency: ad9523: correct kernel-doc bad line warning Insert a "*" in the kernel-doc line to resolve a warning: Warning: include/linux/iio/frequency/ad9523.h:47 bad line: LSB = 1/2 of a period of the divider input clock. Signed-off-by: Randy Dunlap Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- include/linux/iio/frequency/ad9523.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/iio/frequency/ad9523.h b/include/linux/iio/frequency/ad9523.h index ff22a0ac15f5..236437a226b2 100644 --- a/include/linux/iio/frequency/ad9523.h +++ b/include/linux/iio/frequency/ad9523.h @@ -45,7 +45,7 @@ enum ref_sel_mode { * @output_dis: Disables, powers down the entire channel. * @driver_mode: Output driver mode (logic level family). * @divider_phase: Divider initial phase after a SYNC. Range 0..63 - LSB = 1/2 of a period of the divider input clock. + * LSB = 1/2 of a period of the divider input clock. * @channel_divider: 10-bit channel divider. * @extended_name: Optional descriptive channel name. */ From 0077e9b985482e5c020468c6257f8508f68aa0b2 Mon Sep 17 00:00:00 2001 From: Gwendal Grignou Date: Tue, 27 Jan 2026 19:27:02 -0800 Subject: [PATCH 286/297] iio: cros_ec: Allow enabling/disabling calibration mode 'calibrate' was a one-shot event sent to the sensor to calibrate itself. It is used on Bosch sensors (BMI160, BMA254). Light sensors work differently: They are first put in calibration mode, tests are run to collect information and calculate the calibration values to apply. Once done, the sensors are put back in normal mode. Accept boolean true and false (not just true) to enter/exit calibration state. Check "echo 0 > calibrate" is supported. Signed-off-by: Gwendal Grignou Reviewed-by: Nick Vaccaro Reviewed-by: Tzung-Bi Shih Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio-cros-ec | 9 ++++++--- .../common/cros_ec_sensors/cros_ec_sensors_core.c | 6 +++--- include/linux/platform_data/cros_ec_commands.h | 12 +++++++++--- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-bus-iio-cros-ec b/Documentation/ABI/testing/sysfs-bus-iio-cros-ec index 9e3926243797..3de1dfc98389 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio-cros-ec +++ b/Documentation/ABI/testing/sysfs-bus-iio-cros-ec @@ -3,9 +3,12 @@ Date: July 2015 KernelVersion: 4.7 Contact: linux-iio@vger.kernel.org Description: - Writing '1' will perform a FOC (Fast Online Calibration). The - corresponding calibration offsets can be read from `*_calibbias` - entries. + Writing '1' either perform a FOC (Fast Online Calibration) or + enter calibration mode. + Writing '0` exits calibration mode. It is a NOP for FOC enabled + sensors. + The corresponding calibration offsets can be read from `*_calibbias` + entries. What: /sys/bus/iio/devices/iio:deviceX/id Date: September 2017 diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c index ef53066b1735..5133755c2ea6 100644 --- a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c @@ -441,14 +441,14 @@ static ssize_t cros_ec_sensors_calibrate(struct iio_dev *indio_dev, ret = kstrtobool(buf, &calibrate); if (ret < 0) return ret; - if (!calibrate) - return -EINVAL; mutex_lock(&st->cmd_lock); st->param.cmd = MOTIONSENSE_CMD_PERFORM_CALIB; + st->param.perform_calib.enable = calibrate; ret = cros_ec_motion_send_host_cmd(st, 0); if (ret != 0) { - dev_warn(&indio_dev->dev, "Unable to calibrate sensor\n"); + dev_warn(&indio_dev->dev, "Unable to calibrate sensor: %d\n", + ret); } else { /* Save values */ for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++) diff --git a/include/linux/platform_data/cros_ec_commands.h b/include/linux/platform_data/cros_ec_commands.h index 69294f79cc88..d363d60bb8a3 100644 --- a/include/linux/platform_data/cros_ec_commands.h +++ b/include/linux/platform_data/cros_ec_commands.h @@ -2598,13 +2598,19 @@ struct ec_params_motion_sense { /* * Used for MOTIONSENSE_CMD_INFO, MOTIONSENSE_CMD_DATA - * and MOTIONSENSE_CMD_PERFORM_CALIB. */ struct __ec_todo_unpacked { uint8_t sensor_num; - } info, info_3, data, fifo_flush, perform_calib, - list_activities; + } info, info_3, data, fifo_flush, list_activities; + /* + * Used for MOTIONSENSE_CMD_PERFORM_CALIB: + * Allow entering/exiting the calibration mode. + */ + struct __ec_todo_unpacked { + uint8_t sensor_num; + uint8_t enable; + } perform_calib; /* * Used for MOTIONSENSE_CMD_EC_RATE, MOTIONSENSE_CMD_SENSOR_ODR * and MOTIONSENSE_CMD_SENSOR_RANGE. From 36bff1842330b3eddaf5a7977eb00a724fc42c27 Mon Sep 17 00:00:00 2001 From: Shrikant Raskar Date: Wed, 28 Jan 2026 23:21:49 +0530 Subject: [PATCH 287/297] iio: proximity: rfd77402: Align polling timeout with datasheet Update the polling delay to use a 100 ms timeout, as specified in the RFD77402 datasheet. Signed-off-by: Shrikant Raskar Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/proximity/rfd77402.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/iio/proximity/rfd77402.c b/drivers/iio/proximity/rfd77402.c index 4499939215e7..6cae805632a2 100644 --- a/drivers/iio/proximity/rfd77402.c +++ b/drivers/iio/proximity/rfd77402.c @@ -132,7 +132,11 @@ static int rfd77402_measure(struct i2c_client *client) goto err; if (ret & RFD77402_ICSR_RESULT) break; - msleep(20); + /* + * As per RFD77402 datasheet section '3.1.1 Single Measure', + * the suggested timeout value for single measure is 100ms. + */ + msleep(10); } if (tries < 0) { From 51eedb3a323ae87872c4cb60cc6abed696b0a822 Mon Sep 17 00:00:00 2001 From: Shrikant Raskar Date: Wed, 28 Jan 2026 23:21:50 +0530 Subject: [PATCH 288/297] iio: proximity: rfd77402: Use kernel helper for result polling Replace the manually written polling loop with read_poll_timeout(), the kernel's standard helper for waiting on hardware status.This makes the code easier to read. Move the polling logic into a dedicated helper function, as it will be reused by future updates. Reviewed-by: Andy Shevchenko Signed-off-by: Shrikant Raskar Signed-off-by: Jonathan Cameron --- drivers/iio/proximity/rfd77402.c | 46 +++++++++++++++++++------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/drivers/iio/proximity/rfd77402.c b/drivers/iio/proximity/rfd77402.c index 6cae805632a2..3d5643663270 100644 --- a/drivers/iio/proximity/rfd77402.c +++ b/drivers/iio/proximity/rfd77402.c @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -110,10 +111,31 @@ static int rfd77402_set_state(struct i2c_client *client, u8 state, u16 check) return 0; } -static int rfd77402_measure(struct i2c_client *client) +static int rfd77402_wait_for_result(struct rfd77402_data *data) { + struct i2c_client *client = data->client; + int val, ret; + + /* + * As per RFD77402 datasheet section '3.1.1 Single Measure', the + * suggested timeout value for single measure is 100ms. + */ + ret = read_poll_timeout(i2c_smbus_read_byte_data, val, + (val < 0) || (val & RFD77402_ICSR_RESULT), + 10 * USEC_PER_MSEC, + 10 * 10 * USEC_PER_MSEC, + false, + client, RFD77402_ICSR); + if (val < 0) + return val; + + return ret; +} + +static int rfd77402_measure(struct rfd77402_data *data) +{ + struct i2c_client *client = data->client; int ret; - int tries = 10; ret = rfd77402_set_state(client, RFD77402_CMD_MCPU_ON, RFD77402_STATUS_MCPU_ON); @@ -126,23 +148,9 @@ static int rfd77402_measure(struct i2c_client *client) if (ret < 0) goto err; - while (tries-- > 0) { - ret = i2c_smbus_read_byte_data(client, RFD77402_ICSR); - if (ret < 0) - goto err; - if (ret & RFD77402_ICSR_RESULT) - break; - /* - * As per RFD77402 datasheet section '3.1.1 Single Measure', - * the suggested timeout value for single measure is 100ms. - */ - msleep(10); - } - - if (tries < 0) { - ret = -ETIMEDOUT; + ret = rfd77402_wait_for_result(data); + if (ret < 0) goto err; - } ret = i2c_smbus_read_word_data(client, RFD77402_RESULT_R); if (ret < 0) @@ -172,7 +180,7 @@ static int rfd77402_read_raw(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_RAW: mutex_lock(&data->lock); - ret = rfd77402_measure(data->client); + ret = rfd77402_measure(data); mutex_unlock(&data->lock); if (ret < 0) return ret; From dff4bdff074e1f92874676bed57970d19e3a86bd Mon Sep 17 00:00:00 2001 From: Shrikant Raskar Date: Wed, 28 Jan 2026 23:21:51 +0530 Subject: [PATCH 289/297] iio: proximity: rfd77402: Use devm-managed mutex initialization Use devm_mutex_init() to tie the mutex lifetime to the device and improve debugging when CONFIG_DEBUG_MUTEXES is enabled. Reviewed-by: Andy Shevchenko Signed-off-by: Shrikant Raskar Signed-off-by: Jonathan Cameron --- drivers/iio/proximity/rfd77402.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/iio/proximity/rfd77402.c b/drivers/iio/proximity/rfd77402.c index 3d5643663270..eec336bed424 100644 --- a/drivers/iio/proximity/rfd77402.c +++ b/drivers/iio/proximity/rfd77402.c @@ -287,7 +287,10 @@ static int rfd77402_probe(struct i2c_client *client) data = iio_priv(indio_dev); data->client = client; - mutex_init(&data->lock); + + ret = devm_mutex_init(&client->dev, &data->lock); + if (ret) + return ret; indio_dev->info = &rfd77402_info; indio_dev->channels = rfd77402_channels; From 53516b6fdec09cb202ebf52b841cb25599f93688 Mon Sep 17 00:00:00 2001 From: Shrikant Raskar Date: Wed, 28 Jan 2026 23:21:52 +0530 Subject: [PATCH 290/297] iio: proximity: rfd77402: Document device private data structure Add kernel-doc style comments for struct rfd77402_data to describe the purpose of each member. Reviewed-by: Andy Shevchenko Signed-off-by: Shrikant Raskar Signed-off-by: Jonathan Cameron --- drivers/iio/proximity/rfd77402.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/iio/proximity/rfd77402.c b/drivers/iio/proximity/rfd77402.c index eec336bed424..0be49a6e401d 100644 --- a/drivers/iio/proximity/rfd77402.c +++ b/drivers/iio/proximity/rfd77402.c @@ -77,9 +77,13 @@ static const struct { {RFD77402_HFCFG_3, 0x45d4}, }; +/** + * struct rfd77402_data - device-specific data for the RFD77402 sensor + * @client: I2C client handle + * @lock: mutex to serialize sensor reads + */ struct rfd77402_data { struct i2c_client *client; - /* Serialize reads from the sensor */ struct mutex lock; }; From dc81be96a73a67d47da336497b888529c73d389b Mon Sep 17 00:00:00 2001 From: Shrikant Raskar Date: Wed, 28 Jan 2026 23:21:53 +0530 Subject: [PATCH 291/297] iio: proximity: rfd77402: Add interrupt handling support Add interrupt handling support to enable event-driven data acquisition instead of continuous polling. This improves responsiveness, reduces CPU overhead, and supports low-power operation by allowing the system to remain idle until an interrupt occurs. Reviewed-by: Andy Shevchenko Signed-off-by: Shrikant Raskar Signed-off-by: Jonathan Cameron --- drivers/iio/proximity/rfd77402.c | 121 +++++++++++++++++++++++++++++-- 1 file changed, 113 insertions(+), 8 deletions(-) diff --git a/drivers/iio/proximity/rfd77402.c b/drivers/iio/proximity/rfd77402.c index 0be49a6e401d..6afdbfca3e5a 100644 --- a/drivers/iio/proximity/rfd77402.c +++ b/drivers/iio/proximity/rfd77402.c @@ -6,20 +6,28 @@ * * 7-bit I2C slave address 0x4c * - * TODO: interrupt * https://media.digikey.com/pdf/Data%20Sheets/RF%20Digital%20PDFs/RFD77402.pdf */ +#include +#include #include +#include +#include #include +#include #include +#include #include +#include #include #define RFD77402_DRV_NAME "rfd77402" #define RFD77402_ICSR 0x00 /* Interrupt Control Status Register */ +#define RFD77402_ICSR_CLR_CFG BIT(0) +#define RFD77402_ICSR_CLR_TYPE BIT(1) #define RFD77402_ICSR_INT_MODE BIT(2) #define RFD77402_ICSR_INT_POL BIT(3) #define RFD77402_ICSR_RESULT BIT(4) @@ -27,6 +35,12 @@ #define RFD77402_ICSR_H2M_MSG BIT(6) #define RFD77402_ICSR_RESET BIT(7) +#define RFD77402_IER 0x02 +#define RFD77402_IER_RESULT BIT(0) +#define RFD77402_IER_M2H_MSG BIT(1) +#define RFD77402_IER_H2M_MSG BIT(2) +#define RFD77402_IER_RESET BIT(3) + #define RFD77402_CMD_R 0x04 #define RFD77402_CMD_SINGLE 0x01 #define RFD77402_CMD_STANDBY 0x10 @@ -81,10 +95,14 @@ static const struct { * struct rfd77402_data - device-specific data for the RFD77402 sensor * @client: I2C client handle * @lock: mutex to serialize sensor reads + * @completion: completion used for interrupt-driven measurements + * @irq_en: indicates whether interrupt mode is enabled */ struct rfd77402_data { struct i2c_client *client; struct mutex lock; + struct completion completion; + bool irq_en; }; static const struct iio_chan_spec rfd77402_channels[] = { @@ -95,6 +113,41 @@ static const struct iio_chan_spec rfd77402_channels[] = { }, }; +static irqreturn_t rfd77402_interrupt_handler(int irq, void *pdata) +{ + struct rfd77402_data *data = pdata; + int ret; + + ret = i2c_smbus_read_byte_data(data->client, RFD77402_ICSR); + if (ret < 0) + return IRQ_NONE; + + /* Check if the interrupt is from our device */ + if (!(ret & RFD77402_ICSR_RESULT)) + return IRQ_NONE; + + /* Signal completion of measurement */ + complete(&data->completion); + return IRQ_HANDLED; +} + +static int rfd77402_wait_for_irq(struct rfd77402_data *data) +{ + int ret; + + /* + * According to RFD77402 Datasheet v1.8, + * Section 3.1.1 "Single Measure" (Figure: Single Measure Flow Chart), + * the suggested timeout for single measure is 100 ms. + */ + ret = wait_for_completion_timeout(&data->completion, + msecs_to_jiffies(100)); + if (ret == 0) + return -ETIMEDOUT; + + return 0; +} + static int rfd77402_set_state(struct i2c_client *client, u8 state, u16 check) { int ret; @@ -120,6 +173,11 @@ static int rfd77402_wait_for_result(struct rfd77402_data *data) struct i2c_client *client = data->client; int val, ret; + if (data->irq_en) { + reinit_completion(&data->completion); + return rfd77402_wait_for_irq(data); + } + /* * As per RFD77402 datasheet section '3.1.1 Single Measure', the * suggested timeout value for single measure is 100ms. @@ -204,8 +262,20 @@ static const struct iio_info rfd77402_info = { .read_raw = rfd77402_read_raw, }; -static int rfd77402_init(struct i2c_client *client) +static int rfd77402_config_irq(struct i2c_client *client, u8 csr, u8 ier) { + int ret; + + ret = i2c_smbus_write_byte_data(client, RFD77402_ICSR, csr); + if (ret) + return ret; + + return i2c_smbus_write_byte_data(client, RFD77402_IER, ier); +} + +static int rfd77402_init(struct rfd77402_data *data) +{ + struct i2c_client *client = data->client; int ret, i; ret = rfd77402_set_state(client, RFD77402_CMD_STANDBY, @@ -213,10 +283,26 @@ static int rfd77402_init(struct i2c_client *client) if (ret < 0) return ret; - /* configure INT pad as push-pull, active low */ - ret = i2c_smbus_write_byte_data(client, RFD77402_ICSR, - RFD77402_ICSR_INT_MODE); - if (ret < 0) + if (data->irq_en) { + /* + * Enable interrupt mode: + * - Configure ICSR for auto-clear on read and + * push-pull output + * - Enable "result ready" interrupt in IER + */ + ret = rfd77402_config_irq(client, + RFD77402_ICSR_CLR_CFG | + RFD77402_ICSR_INT_MODE, + RFD77402_IER_RESULT); + } else { + /* + * Disable all interrupts: + * - Clear ICSR configuration + * - Disable all interrupts in IER + */ + ret = rfd77402_config_irq(client, 0, 0); + } + if (ret) return ret; /* I2C configuration */ @@ -296,13 +382,29 @@ static int rfd77402_probe(struct i2c_client *client) if (ret) return ret; + init_completion(&data->completion); + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, rfd77402_interrupt_handler, + IRQF_ONESHOT, + "rfd77402", data); + if (ret) + return ret; + + data->irq_en = true; + dev_dbg(&client->dev, "Using interrupt mode\n"); + } else { + dev_dbg(&client->dev, "Using polling mode\n"); + } + indio_dev->info = &rfd77402_info; indio_dev->channels = rfd77402_channels; indio_dev->num_channels = ARRAY_SIZE(rfd77402_channels); indio_dev->name = RFD77402_DRV_NAME; indio_dev->modes = INDIO_DIRECT_MODE; - ret = rfd77402_init(client); + ret = rfd77402_init(data); if (ret < 0) return ret; @@ -320,7 +422,10 @@ static int rfd77402_suspend(struct device *dev) static int rfd77402_resume(struct device *dev) { - return rfd77402_init(to_i2c_client(dev)); + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct rfd77402_data *data = iio_priv(indio_dev); + + return rfd77402_init(data); } static DEFINE_SIMPLE_DEV_PM_OPS(rfd77402_pm_ops, rfd77402_suspend, From 62b44ebc1f2c71db3ca2d4737c52e433f6f03038 Mon Sep 17 00:00:00 2001 From: Harshit Mogalapalli Date: Tue, 27 Jan 2026 22:49:49 -0800 Subject: [PATCH 292/297] iio: sca3000: Fix a resource leak in sca3000_probe() spi->irq from request_threaded_irq() not released when iio_device_register() fails. Add an return value check and jump to a common error handler when iio_device_register() fails. Fixes: 9a4936dc89a3 ("staging:iio:accel:sca3000 Tidy up probe order to avoid a race.") Signed-off-by: Harshit Mogalapalli Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/accel/sca3000.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/iio/accel/sca3000.c b/drivers/iio/accel/sca3000.c index 950b4c672484..4a827be439a2 100644 --- a/drivers/iio/accel/sca3000.c +++ b/drivers/iio/accel/sca3000.c @@ -1487,7 +1487,11 @@ static int sca3000_probe(struct spi_device *spi) if (ret) goto error_free_irq; - return iio_device_register(indio_dev); + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_irq; + + return 0; error_free_irq: if (spi->irq) From 0713b26190addfa3a774b386c8658952ef9f7faf Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 31 Jan 2026 12:19:05 +0000 Subject: [PATCH 293/297] iio: magn: mmc5633: Fix Kconfig for combination of I3C as module and driver builtin Fix based on similar fix in: commit 83b645ee43f7 ("hwmon: tmp108: fix I3C dependency") Note to keep things simple I'm now requiring I2C. That can probably be relaxed in future, but I want to reduce the test set of builds for now given this is blocker for the main IIO pull request. Fixes: e559c8641460 ("iio: magn: mmc5633: Ensure REGMAP_I2C / I3C not build if I2C / I3C is not.") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202601310904.DueZdiuY-lkp@intel.com/ Signed-off-by: Jonathan Cameron Reviewed-by: Frank Li --- drivers/iio/magnetometer/Kconfig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig index 2b81b22c9550..9345fb6d5317 100644 --- a/drivers/iio/magnetometer/Kconfig +++ b/drivers/iio/magnetometer/Kconfig @@ -141,9 +141,10 @@ config MMC35240 config MMC5633 tristate "MEMSIC MMC5633 3-axis magnetic sensor" - select REGMAP_I2C if I2C + select REGMAP_I2C select REGMAP_I3C if I3C - depends on I2C || I3C + depends on I2C + depends on I3C || !I3C help Say yes here to build support for the MEMSIC MMC5633 3-axis magnetic sensor. From 38ac9179a79d81ab1db3807ddd6c03fab29eb19b Mon Sep 17 00:00:00 2001 From: Shivam Kalra Date: Fri, 30 Jan 2026 23:58:23 +0530 Subject: [PATCH 294/297] rust_binder: fix needless borrow in context.rs Clippy warns about a needless borrow in context.rs: error: this expression creates a reference which is immediately dereferenced by the compiler --> drivers/android/binder/context.rs:141:18 | 141 | func(&proc); | ^^^^^ help: change this to: `proc` Remove the unnecessary borrow to satisfy clippy and improve code cleanliness. No functional change. Signed-off-by: Shivam Kalra Acked-by: Alice Ryhl Link: https://patch.msgid.link/20260130182842.217821-1-shivamklr@cock.li Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/android/binder/context.rs b/drivers/android/binder/context.rs index c7b75efef217..9cf437c025a2 100644 --- a/drivers/android/binder/context.rs +++ b/drivers/android/binder/context.rs @@ -138,7 +138,7 @@ impl Context { { let lock = self.manager.lock(); for proc in &lock.all_procs { - func(&proc); + func(proc); } } From 9caa30dada9e8ec9b1b09cf95e6d1a806c86d101 Mon Sep 17 00:00:00 2001 From: Shankari Anand Date: Sat, 3 Jan 2026 01:57:11 +0530 Subject: [PATCH 295/297] drivers: android: binder: Update ARef imports from sync::aref Update call sites in binder files to import `ARef` from `sync::aref` instead of `types`. This aligns with the ongoing effort to move `ARef` and `AlwaysRefCounted` to sync. Suggested-by: Benno Lossin Link: https://github.com/Rust-for-Linux/linux/issues/1173 Signed-off-by: Shankari Anand Acked-by: Alice Ryhl Link: https://patch.msgid.link/20260102202714.184223-2-shankari.ak0208@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/process.rs | 2 +- drivers/android/binder/thread.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs index ba98ef9f3545..41de5593197c 100644 --- a/drivers/android/binder/process.rs +++ b/drivers/android/binder/process.rs @@ -28,11 +28,11 @@ use kernel::{ seq_print, sync::poll::PollTable, sync::{ + aref::ARef, lock::{spinlock::SpinLockBackend, Guard}, Arc, ArcBorrow, CondVar, CondVarTimeoutResult, Mutex, SpinLock, UniqueArc, }, task::Task, - types::ARef, uaccess::{UserSlice, UserSliceReader}, uapi, workqueue::{self, Work}, diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs index 6e8f29e50cac..23697ae896ee 100644 --- a/drivers/android/binder/thread.rs +++ b/drivers/android/binder/thread.rs @@ -16,9 +16,8 @@ use kernel::{ seq_file::SeqFile, seq_print, sync::poll::{PollCondVar, PollTable}, - sync::{Arc, SpinLock}, + sync::{aref::ARef, Arc, SpinLock}, task::Task, - types::ARef, uaccess::UserSlice, uapi, }; From 4df29fb5bcebeea28b29386dec18355949512ca1 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 28 Jan 2026 09:12:07 +0000 Subject: [PATCH 296/297] rust_binder: return p from rust_binder_transaction_target_node() Somehow this got changed to NULL when I ported this to upstream it. No idea how that happened. Reported-by: Carlos Llamas Closes: https://lore.kernel.org/r/aXkEiC1sGOGfDuzI@google.com Fixes: c1ea31205edf ("rust_binder: add binder_transaction tracepoint") Signed-off-by: Alice Ryhl Acked-by: Carlos Llamas Link: https://patch.msgid.link/20260128-binder-fix-target-node-null-v1-1-78d198ef55a5@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/rust_binder.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/android/binder/rust_binder.h b/drivers/android/binder/rust_binder.h index e68ba7a23c34..d2284726c025 100644 --- a/drivers/android/binder/rust_binder.h +++ b/drivers/android/binder/rust_binder.h @@ -79,7 +79,7 @@ static inline rust_binder_node rust_binder_transaction_target_node(rust_binder_t if (p) p = p + RUST_BINDER_LAYOUT.n.arc_offset; - return NULL; + return p; } static inline rust_binder_process rust_binder_transaction_to_proc(rust_binder_transaction t) From 3c4ae63073d84abee5d81ce46d86a94e9dae9c89 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 27 Nov 2025 14:47:02 +0100 Subject: [PATCH 297/297] mux: mmio: fix regmap leak on probe failure The mmio regmap that may be allocated during probe is never freed. Switch to using the device managed allocator so that the regmap is released on probe failures (e.g. probe deferral) and on driver unbind. Fixes: 61de83fd8256 ("mux: mmio: Do not use syscon helper to build regmap") Cc: stable@vger.kernel.org # 6.16 Cc: Andrew Davis Signed-off-by: Johan Hovold Acked-by: Andrew Davis Link: https://patch.msgid.link/20251127134702.1915-1-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/mux/mmio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mux/mmio.c b/drivers/mux/mmio.c index 3409af1ffb80..0611ef28bb69 100644 --- a/drivers/mux/mmio.c +++ b/drivers/mux/mmio.c @@ -72,7 +72,7 @@ static int mux_mmio_probe(struct platform_device *pdev) if (IS_ERR(base)) regmap = ERR_PTR(-ENODEV); else - regmap = regmap_init_mmio(dev, base, &mux_mmio_regmap_cfg); + regmap = devm_regmap_init_mmio(dev, base, &mux_mmio_regmap_cfg); /* Fallback to checking the parent node on "real" errors. */ if (IS_ERR(regmap) && regmap != ERR_PTR(-EPROBE_DEFER)) { regmap = dev_get_regmap(dev->parent, NULL);