* [PATCH v6 4/8] clk: qcom: tcsrcc-glymur: Add regulator supplies and migrate to clk_ref helper
From: Qiang Yu @ 2026-06-22 5:11 UTC (permalink / raw)
To: Bjorn Andersson, Michael Turquette, Stephen Boyd, Brian Masney,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Taniya Das,
Konrad Dybcio
Cc: linux-arm-msm, linux-clk, devicetree, linux-kernel, Qiang Yu,
krishna.chundru, Konrad Dybcio
In-Reply-To: <20260621-tcsr_qref_0622-v6-0-c939c22ded0c@oss.qualcomm.com>
Replace local clk_branch-based clkref definitions with descriptor-based
registration via qcom_clk_ref_probe().
This keeps the glymur driver focused on clock metadata and reuses common
runtime logic for regulator handling, enable/disable sequencing, and OF
provider wiring.
Co-developed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Signed-off-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
---
drivers/clk/qcom/tcsrcc-glymur.c | 361 +++++++++++++--------------------------
1 file changed, 116 insertions(+), 245 deletions(-)
diff --git a/drivers/clk/qcom/tcsrcc-glymur.c b/drivers/clk/qcom/tcsrcc-glymur.c
index 9c0edebcdbb1..2db039342f30 100644
--- a/drivers/clk/qcom/tcsrcc-glymur.c
+++ b/drivers/clk/qcom/tcsrcc-glymur.c
@@ -4,277 +4,146 @@
*/
#include <linux/clk-provider.h>
+#include <linux/clk/qcom.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <dt-bindings/clock/qcom,glymur-tcsr.h>
-#include "clk-alpha-pll.h"
-#include "clk-branch.h"
-#include "clk-pll.h"
-#include "clk-rcg.h"
-#include "clk-regmap.h"
-#include "clk-regmap-divider.h"
-#include "clk-regmap-mux.h"
-#include "common.h"
-#include "gdsc.h"
-#include "reset.h"
-
-enum {
- DT_BI_TCXO_PAD,
+static const char * const glymur_tcsr_tx0_rx5_regulators[] = {
+ "vdda-refgen3-0p9",
+ "vdda-refgen3-1p2",
+ "vdda-qrefrx5-0p9",
+ "vdda-qreftx0-0p9",
+ "vdda-qreftx0-1p2",
};
-static struct clk_branch tcsr_edp_clkref_en = {
- .halt_reg = 0x60,
- .halt_check = BRANCH_HALT_DELAY,
- .clkr = {
- .enable_reg = 0x60,
- .enable_mask = BIT(0),
- .hw.init = &(const struct clk_init_data) {
- .name = "tcsr_edp_clkref_en",
- .parent_data = &(const struct clk_parent_data){
- .index = DT_BI_TCXO_PAD,
- },
- .num_parents = 1,
- .ops = &clk_branch2_ops,
- },
- },
+static const char * const glymur_tcsr_tx1_rpt0_rx0_regulators[] = {
+ "vdda-refgen4-0p9",
+ "vdda-refgen4-1p2",
+ "vdda-qreftx1-0p9",
+ "vdda-qrefrpt0-0p9",
+ "vdda-qrefrx0-0p9",
};
-static struct clk_branch tcsr_pcie_1_clkref_en = {
- .halt_reg = 0x48,
- .halt_check = BRANCH_HALT_DELAY,
- .clkr = {
- .enable_reg = 0x48,
- .enable_mask = BIT(0),
- .hw.init = &(const struct clk_init_data) {
- .name = "tcsr_pcie_1_clkref_en",
- .parent_data = &(const struct clk_parent_data){
- .index = DT_BI_TCXO_PAD,
- },
- .num_parents = 1,
- .ops = &clk_branch2_ops,
- },
- },
+static const char * const glymur_tcsr_tx1_rpt01_rx1_regulators[] = {
+ "vdda-refgen4-0p9",
+ "vdda-refgen4-1p2",
+ "vdda-qreftx1-0p9",
+ "vdda-qrefrpt0-0p9",
+ "vdda-qrefrpt1-0p9",
+ "vdda-qrefrx1-0p9",
};
-static struct clk_branch tcsr_pcie_2_clkref_en = {
- .halt_reg = 0x4c,
- .halt_check = BRANCH_HALT_DELAY,
- .clkr = {
- .enable_reg = 0x4c,
- .enable_mask = BIT(0),
- .hw.init = &(const struct clk_init_data) {
- .name = "tcsr_pcie_2_clkref_en",
- .parent_data = &(const struct clk_parent_data){
- .index = DT_BI_TCXO_PAD,
- },
- .num_parents = 1,
- .ops = &clk_branch2_ops,
- },
- },
+static const char * const glymur_tcsr_tx1_rpt012_rx2_regulators[] = {
+ "vdda-refgen4-0p9",
+ "vdda-refgen4-1p2",
+ "vdda-qreftx1-0p9",
+ "vdda-qrefrpt0-0p9",
+ "vdda-qrefrpt1-0p9",
+ "vdda-qrefrpt2-0p9",
+ "vdda-qrefrx2-0p9",
};
-static struct clk_branch tcsr_pcie_3_clkref_en = {
- .halt_reg = 0x54,
- .halt_check = BRANCH_HALT_DELAY,
- .clkr = {
- .enable_reg = 0x54,
- .enable_mask = BIT(0),
- .hw.init = &(const struct clk_init_data) {
- .name = "tcsr_pcie_3_clkref_en",
- .parent_data = &(const struct clk_parent_data){
- .index = DT_BI_TCXO_PAD,
- },
- .num_parents = 1,
- .ops = &clk_branch2_ops,
- },
- },
+static const char * const glymur_tcsr_tx1_rpt34_rx4_regulators[] = {
+ "vdda-refgen4-0p9",
+ "vdda-refgen4-1p2",
+ "vdda-qreftx1-0p9",
+ "vdda-qrefrpt3-0p9",
+ "vdda-qrefrpt4-0p9",
+ "vdda-qrefrx4-0p9",
};
-static struct clk_branch tcsr_pcie_4_clkref_en = {
- .halt_reg = 0x58,
- .halt_check = BRANCH_HALT_DELAY,
- .clkr = {
- .enable_reg = 0x58,
- .enable_mask = BIT(0),
- .hw.init = &(const struct clk_init_data) {
- .name = "tcsr_pcie_4_clkref_en",
- .parent_data = &(const struct clk_parent_data){
- .index = DT_BI_TCXO_PAD,
- },
- .num_parents = 1,
- .ops = &clk_branch2_ops,
- },
- },
+static const struct regmap_config tcsr_cc_glymur_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x94,
+ .fast_io = true,
};
-static struct clk_branch tcsr_usb2_1_clkref_en = {
- .halt_reg = 0x6c,
- .halt_check = BRANCH_HALT_DELAY,
- .clkr = {
- .enable_reg = 0x6c,
- .enable_mask = BIT(0),
- .hw.init = &(const struct clk_init_data) {
- .name = "tcsr_usb2_1_clkref_en",
- .parent_data = &(const struct clk_parent_data){
- .index = DT_BI_TCXO_PAD,
- },
- .num_parents = 1,
- .ops = &clk_branch2_ops,
- },
+static const struct qcom_clk_ref_desc tcsr_cc_glymur_clk_descs[] = {
+ [TCSR_EDP_CLKREF_EN] = {
+ .name = "tcsr_edp_clkref_en",
+ .offset = 0x60,
+ .regulator_names = glymur_tcsr_tx1_rpt0_rx0_regulators,
+ .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt0_rx0_regulators),
},
-};
-
-static struct clk_branch tcsr_usb2_2_clkref_en = {
- .halt_reg = 0x70,
- .halt_check = BRANCH_HALT_DELAY,
- .clkr = {
- .enable_reg = 0x70,
- .enable_mask = BIT(0),
- .hw.init = &(const struct clk_init_data) {
- .name = "tcsr_usb2_2_clkref_en",
- .parent_data = &(const struct clk_parent_data){
- .index = DT_BI_TCXO_PAD,
- },
- .num_parents = 1,
- .ops = &clk_branch2_ops,
- },
+ [TCSR_PCIE_1_CLKREF_EN] = {
+ .name = "tcsr_pcie_1_clkref_en",
+ .offset = 0x48,
+ .regulator_names = glymur_tcsr_tx0_rx5_regulators,
+ .num_regulators = ARRAY_SIZE(glymur_tcsr_tx0_rx5_regulators),
},
-};
-
-static struct clk_branch tcsr_usb2_3_clkref_en = {
- .halt_reg = 0x74,
- .halt_check = BRANCH_HALT_DELAY,
- .clkr = {
- .enable_reg = 0x74,
- .enable_mask = BIT(0),
- .hw.init = &(const struct clk_init_data) {
- .name = "tcsr_usb2_3_clkref_en",
- .parent_data = &(const struct clk_parent_data){
- .index = DT_BI_TCXO_PAD,
- },
- .num_parents = 1,
- .ops = &clk_branch2_ops,
- },
+ [TCSR_PCIE_2_CLKREF_EN] = {
+ .name = "tcsr_pcie_2_clkref_en",
+ .offset = 0x4c,
+ .regulator_names = glymur_tcsr_tx1_rpt012_rx2_regulators,
+ .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt012_rx2_regulators),
},
-};
-
-static struct clk_branch tcsr_usb2_4_clkref_en = {
- .halt_reg = 0x88,
- .halt_check = BRANCH_HALT_DELAY,
- .clkr = {
- .enable_reg = 0x88,
- .enable_mask = BIT(0),
- .hw.init = &(const struct clk_init_data) {
- .name = "tcsr_usb2_4_clkref_en",
- .parent_data = &(const struct clk_parent_data){
- .index = DT_BI_TCXO_PAD,
- },
- .num_parents = 1,
- .ops = &clk_branch2_ops,
- },
+ [TCSR_PCIE_3_CLKREF_EN] = {
+ .name = "tcsr_pcie_3_clkref_en",
+ .offset = 0x54,
+ .regulator_names = glymur_tcsr_tx1_rpt01_rx1_regulators,
+ .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt01_rx1_regulators),
},
-};
-
-static struct clk_branch tcsr_usb3_0_clkref_en = {
- .halt_reg = 0x64,
- .halt_check = BRANCH_HALT_DELAY,
- .clkr = {
- .enable_reg = 0x64,
- .enable_mask = BIT(0),
- .hw.init = &(const struct clk_init_data) {
- .name = "tcsr_usb3_0_clkref_en",
- .parent_data = &(const struct clk_parent_data){
- .index = DT_BI_TCXO_PAD,
- },
- .num_parents = 1,
- .ops = &clk_branch2_ops,
- },
+ [TCSR_PCIE_4_CLKREF_EN] = {
+ .name = "tcsr_pcie_4_clkref_en",
+ .offset = 0x58,
+ .regulator_names = glymur_tcsr_tx1_rpt012_rx2_regulators,
+ .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt012_rx2_regulators),
},
-};
-
-static struct clk_branch tcsr_usb3_1_clkref_en = {
- .halt_reg = 0x68,
- .halt_check = BRANCH_HALT_DELAY,
- .clkr = {
- .enable_reg = 0x68,
- .enable_mask = BIT(0),
- .hw.init = &(const struct clk_init_data) {
- .name = "tcsr_usb3_1_clkref_en",
- .parent_data = &(const struct clk_parent_data){
- .index = DT_BI_TCXO_PAD,
- },
- .num_parents = 1,
- .ops = &clk_branch2_ops,
- },
+ [TCSR_USB2_1_CLKREF_EN] = {
+ .name = "tcsr_usb2_1_clkref_en",
+ .offset = 0x6c,
+ .regulator_names = glymur_tcsr_tx1_rpt34_rx4_regulators,
+ .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt34_rx4_regulators),
},
-};
-
-static struct clk_branch tcsr_usb4_1_clkref_en = {
- .halt_reg = 0x44,
- .halt_check = BRANCH_HALT_DELAY,
- .clkr = {
- .enable_reg = 0x44,
- .enable_mask = BIT(0),
- .hw.init = &(const struct clk_init_data) {
- .name = "tcsr_usb4_1_clkref_en",
- .parent_data = &(const struct clk_parent_data){
- .index = DT_BI_TCXO_PAD,
- },
- .num_parents = 1,
- .ops = &clk_branch2_ops,
- },
+ [TCSR_USB2_2_CLKREF_EN] = {
+ .name = "tcsr_usb2_2_clkref_en",
+ .offset = 0x70,
+ .regulator_names = glymur_tcsr_tx1_rpt01_rx1_regulators,
+ .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt01_rx1_regulators),
},
-};
-
-static struct clk_branch tcsr_usb4_2_clkref_en = {
- .halt_reg = 0x5c,
- .halt_check = BRANCH_HALT_DELAY,
- .clkr = {
- .enable_reg = 0x5c,
- .enable_mask = BIT(0),
- .hw.init = &(const struct clk_init_data) {
- .name = "tcsr_usb4_2_clkref_en",
- .parent_data = &(const struct clk_parent_data){
- .index = DT_BI_TCXO_PAD,
- },
- .num_parents = 1,
- .ops = &clk_branch2_ops,
- },
+ [TCSR_USB2_3_CLKREF_EN] = {
+ .name = "tcsr_usb2_3_clkref_en",
+ .offset = 0x74,
+ .regulator_names = glymur_tcsr_tx1_rpt34_rx4_regulators,
+ .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt34_rx4_regulators),
+ },
+ [TCSR_USB2_4_CLKREF_EN] = {
+ .name = "tcsr_usb2_4_clkref_en",
+ .offset = 0x88,
+ .regulator_names = glymur_tcsr_tx1_rpt34_rx4_regulators,
+ .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt34_rx4_regulators),
+ },
+ [TCSR_USB3_0_CLKREF_EN] = {
+ .name = "tcsr_usb3_0_clkref_en",
+ .offset = 0x64,
+ .regulator_names = glymur_tcsr_tx1_rpt34_rx4_regulators,
+ .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt34_rx4_regulators),
+ },
+ [TCSR_USB3_1_CLKREF_EN] = {
+ .name = "tcsr_usb3_1_clkref_en",
+ .offset = 0x68,
+ .regulator_names = glymur_tcsr_tx1_rpt34_rx4_regulators,
+ .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt34_rx4_regulators),
+ },
+ [TCSR_USB4_1_CLKREF_EN] = {
+ .name = "tcsr_usb4_1_clkref_en",
+ .offset = 0x44,
+ .regulator_names = glymur_tcsr_tx0_rx5_regulators,
+ .num_regulators = ARRAY_SIZE(glymur_tcsr_tx0_rx5_regulators),
+ },
+ [TCSR_USB4_2_CLKREF_EN] = {
+ .name = "tcsr_usb4_2_clkref_en",
+ .offset = 0x5c,
+ .regulator_names = glymur_tcsr_tx1_rpt01_rx1_regulators,
+ .num_regulators = ARRAY_SIZE(glymur_tcsr_tx1_rpt01_rx1_regulators),
},
-};
-
-static struct clk_regmap *tcsr_cc_glymur_clocks[] = {
- [TCSR_EDP_CLKREF_EN] = &tcsr_edp_clkref_en.clkr,
- [TCSR_PCIE_1_CLKREF_EN] = &tcsr_pcie_1_clkref_en.clkr,
- [TCSR_PCIE_2_CLKREF_EN] = &tcsr_pcie_2_clkref_en.clkr,
- [TCSR_PCIE_3_CLKREF_EN] = &tcsr_pcie_3_clkref_en.clkr,
- [TCSR_PCIE_4_CLKREF_EN] = &tcsr_pcie_4_clkref_en.clkr,
- [TCSR_USB2_1_CLKREF_EN] = &tcsr_usb2_1_clkref_en.clkr,
- [TCSR_USB2_2_CLKREF_EN] = &tcsr_usb2_2_clkref_en.clkr,
- [TCSR_USB2_3_CLKREF_EN] = &tcsr_usb2_3_clkref_en.clkr,
- [TCSR_USB2_4_CLKREF_EN] = &tcsr_usb2_4_clkref_en.clkr,
- [TCSR_USB3_0_CLKREF_EN] = &tcsr_usb3_0_clkref_en.clkr,
- [TCSR_USB3_1_CLKREF_EN] = &tcsr_usb3_1_clkref_en.clkr,
- [TCSR_USB4_1_CLKREF_EN] = &tcsr_usb4_1_clkref_en.clkr,
- [TCSR_USB4_2_CLKREF_EN] = &tcsr_usb4_2_clkref_en.clkr,
-};
-
-static const struct regmap_config tcsr_cc_glymur_regmap_config = {
- .reg_bits = 32,
- .reg_stride = 4,
- .val_bits = 32,
- .max_register = 0x94,
- .fast_io = true,
-};
-
-static const struct qcom_cc_desc tcsr_cc_glymur_desc = {
- .config = &tcsr_cc_glymur_regmap_config,
- .clks = tcsr_cc_glymur_clocks,
- .num_clks = ARRAY_SIZE(tcsr_cc_glymur_clocks),
};
static const struct of_device_id tcsr_cc_glymur_match_table[] = {
@@ -285,7 +154,9 @@ MODULE_DEVICE_TABLE(of, tcsr_cc_glymur_match_table);
static int tcsr_cc_glymur_probe(struct platform_device *pdev)
{
- return qcom_cc_probe(pdev, &tcsr_cc_glymur_desc);
+ return qcom_clk_ref_probe(pdev, &tcsr_cc_glymur_regmap_config,
+ tcsr_cc_glymur_clk_descs,
+ ARRAY_SIZE(tcsr_cc_glymur_clk_descs));
}
static struct platform_driver tcsr_cc_glymur_driver = {
--
2.34.1
^ permalink raw reply related
* [PATCH v6 3/8] clk: qcom: Add generic clkref_en support
From: Qiang Yu @ 2026-06-22 5:11 UTC (permalink / raw)
To: Bjorn Andersson, Michael Turquette, Stephen Boyd, Brian Masney,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Taniya Das,
Konrad Dybcio
Cc: linux-arm-msm, linux-clk, devicetree, linux-kernel, Qiang Yu,
krishna.chundru
In-Reply-To: <20260621-tcsr_qref_0622-v6-0-c939c22ded0c@oss.qualcomm.com>
Before XO refclk is distributed to PCIe/USB/eDP PHYs, it passes through
a QREF block. QREF is powered by dedicated LDO rails, and the clkref_en
register controls whether refclk is gated through to the PHY side.
These clkref controls are different from typical GCC branch clocks:
- only a single enable bit is present, without branch-style config bits
- regulators must be voted before enable and unvoted after disable
Model this as a dedicated clk_ref clock type with custom clk_ops instead
of reusing struct clk_branch semantics.
Also provide a common registration/probe API so the same clkref model
can be reused regardless of where clkref_en registers are placed, e.g.
TCSR on glymur and TLMM on SM8750.
Signed-off-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
---
drivers/clk/qcom/Makefile | 1 +
drivers/clk/qcom/clk-ref.c | 205 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/clk/qcom.h | 67 +++++++++++++++
3 files changed, 273 insertions(+)
diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
index 58f9a5eb6fd7..4e7abe8d49e6 100644
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -8,6 +8,7 @@ clk-qcom-y += clk-pll.o
clk-qcom-y += clk-rcg.o
clk-qcom-y += clk-rcg2.o
clk-qcom-y += clk-branch.o
+clk-qcom-y += clk-ref.o
clk-qcom-y += clk-regmap-divider.o
clk-qcom-y += clk-regmap-mux.o
clk-qcom-y += clk-regmap-mux-div.o
diff --git a/drivers/clk/qcom/clk-ref.c b/drivers/clk/qcom/clk-ref.c
new file mode 100644
index 000000000000..8132e510f43c
--- /dev/null
+++ b/drivers/clk/qcom/clk-ref.c
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clk/qcom.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#define QCOM_CLK_REF_EN_MASK BIT(0)
+
+struct qcom_clk_ref_provider {
+ struct qcom_clk_ref *refs;
+ size_t num_refs;
+};
+
+static inline struct qcom_clk_ref *to_qcom_clk_ref(struct clk_hw *hw)
+{
+ return container_of(hw, struct qcom_clk_ref, hw);
+}
+
+static const struct clk_parent_data qcom_clk_ref_parent_data = {
+ .index = 0,
+};
+
+static int qcom_clk_ref_prepare(struct clk_hw *hw)
+{
+ struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw);
+ int ret;
+
+ if (!rclk->desc.num_regulators)
+ return 0;
+
+ ret = regulator_bulk_enable(rclk->desc.num_regulators, rclk->regulators);
+ if (ret)
+ pr_err("Failed to enable regulators for %s: %d\n",
+ clk_hw_get_name(hw), ret);
+
+ return ret;
+}
+
+static void qcom_clk_ref_unprepare(struct clk_hw *hw)
+{
+ struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw);
+
+ if (rclk->desc.num_regulators)
+ regulator_bulk_disable(rclk->desc.num_regulators, rclk->regulators);
+}
+
+static int qcom_clk_ref_enable(struct clk_hw *hw)
+{
+ struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw);
+ int ret;
+
+ ret = regmap_set_bits(rclk->regmap, rclk->desc.offset, QCOM_CLK_REF_EN_MASK);
+ if (ret)
+ return ret;
+
+ udelay(10);
+
+ return 0;
+}
+
+static void qcom_clk_ref_disable(struct clk_hw *hw)
+{
+ struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw);
+
+ regmap_clear_bits(rclk->regmap, rclk->desc.offset, QCOM_CLK_REF_EN_MASK);
+ udelay(10);
+}
+
+static int qcom_clk_ref_is_enabled(struct clk_hw *hw)
+{
+ struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw);
+ u32 val;
+ int ret;
+
+ ret = regmap_read(rclk->regmap, rclk->desc.offset, &val);
+ if (ret)
+ return 0;
+
+ return !!(val & QCOM_CLK_REF_EN_MASK);
+}
+
+static const struct clk_ops qcom_clk_ref_ops = {
+ .prepare = qcom_clk_ref_prepare,
+ .unprepare = qcom_clk_ref_unprepare,
+ .enable = qcom_clk_ref_enable,
+ .disable = qcom_clk_ref_disable,
+ .is_enabled = qcom_clk_ref_is_enabled,
+};
+
+static int qcom_clk_ref_register(struct device *dev, struct regmap *regmap,
+ struct qcom_clk_ref *clk_refs,
+ const struct qcom_clk_ref_desc *descs,
+ size_t num_clk_refs)
+{
+ const struct qcom_clk_ref_desc *desc;
+ struct clk_init_data init_data = {};
+ struct qcom_clk_ref *clk_ref;
+ size_t clk_idx;
+ unsigned int i;
+ int ret;
+
+ for (clk_idx = 0; clk_idx < num_clk_refs; clk_idx++) {
+ clk_ref = &clk_refs[clk_idx];
+ desc = &descs[clk_idx];
+
+ if (!desc->name)
+ continue;
+
+ clk_ref->regmap = regmap;
+ clk_ref->desc = *desc;
+
+ if (clk_ref->desc.num_regulators) {
+ clk_ref->regulators = devm_kcalloc(dev, clk_ref->desc.num_regulators,
+ sizeof(*clk_ref->regulators),
+ GFP_KERNEL);
+ if (!clk_ref->regulators)
+ return -ENOMEM;
+
+ for (i = 0; i < clk_ref->desc.num_regulators; i++)
+ clk_ref->regulators[i].supply =
+ clk_ref->desc.regulator_names[i];
+
+ ret = devm_regulator_bulk_get(dev, clk_ref->desc.num_regulators,
+ clk_ref->regulators);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to get regulators for %s\n",
+ clk_ref->desc.name);
+ }
+
+ init_data.name = clk_ref->desc.name;
+ init_data.parent_data = &qcom_clk_ref_parent_data;
+ init_data.num_parents = 1;
+ init_data.ops = &qcom_clk_ref_ops;
+ clk_ref->hw.init = &init_data;
+
+ ret = devm_clk_hw_register(dev, &clk_ref->hw);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct clk_hw *qcom_clk_ref_provider_get(struct of_phandle_args *clkspec, void *data)
+{
+ struct qcom_clk_ref_provider *provider = data;
+ unsigned int idx = clkspec->args[0];
+
+ if (idx >= provider->num_refs)
+ return ERR_PTR(-EINVAL);
+
+ if (!provider->refs[idx].regmap)
+ return ERR_PTR(-ENOENT);
+
+ return &provider->refs[idx].hw;
+}
+
+int qcom_clk_ref_probe(struct platform_device *pdev,
+ const struct regmap_config *config,
+ const struct qcom_clk_ref_desc *descs,
+ size_t num_clk_refs)
+{
+ struct qcom_clk_ref_provider *provider;
+ struct device *dev = &pdev->dev;
+ struct regmap *regmap;
+ void __iomem *base;
+ int ret;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ regmap = devm_regmap_init_mmio(dev, base, config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ provider = devm_kzalloc(dev, sizeof(*provider), GFP_KERNEL);
+ if (!provider)
+ return -ENOMEM;
+
+ provider->refs = devm_kcalloc(dev, num_clk_refs, sizeof(*provider->refs),
+ GFP_KERNEL);
+ if (!provider->refs)
+ return -ENOMEM;
+
+ provider->num_refs = num_clk_refs;
+
+ ret = qcom_clk_ref_register(dev, regmap, provider->refs, descs,
+ provider->num_refs);
+ if (ret)
+ return ret;
+
+ return devm_of_clk_add_hw_provider(dev, qcom_clk_ref_provider_get, provider);
+}
+EXPORT_SYMBOL_GPL(qcom_clk_ref_probe);
diff --git a/include/linux/clk/qcom.h b/include/linux/clk/qcom.h
new file mode 100644
index 000000000000..a89122a8f87b
--- /dev/null
+++ b/include/linux/clk/qcom.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __LINUX_CLK_QCOM_H
+#define __LINUX_CLK_QCOM_H
+
+#include <linux/clk-provider.h>
+#include <linux/errno.h>
+#include <linux/kconfig.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+struct device;
+struct platform_device;
+struct regulator_bulk_data;
+
+/**
+ * struct qcom_clk_ref_desc - descriptor for a clkref_en gate clock
+ * @name: clock name exposed to the common clock framework
+ * @offset: clkref_en register offset from the block base
+ * @regulator_names: optional supply names enabled while preparing the clock
+ * @num_regulators: number of entries in @regulator_names
+ */
+struct qcom_clk_ref_desc {
+ const char *name;
+ u32 offset;
+ const char * const *regulator_names;
+ unsigned int num_regulators;
+};
+
+/**
+ * struct qcom_clk_ref - per-clock data for a clkref_en gate clock
+ * @hw: common clock framework hardware clock handle
+ * @regmap: register map backing the clkref_en register
+ * @desc: clock descriptor copied at registration time
+ * @regulators: optional bulk regulator handles for @desc.regulator_names
+ */
+struct qcom_clk_ref {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ struct qcom_clk_ref_desc desc;
+ struct regulator_bulk_data *regulators;
+};
+
+#if IS_ENABLED(CONFIG_COMMON_CLK_QCOM)
+
+int qcom_clk_ref_probe(struct platform_device *pdev,
+ const struct regmap_config *config,
+ const struct qcom_clk_ref_desc *descs,
+ size_t num_clk_refs);
+
+#else
+
+static inline int
+qcom_clk_ref_probe(struct platform_device *pdev,
+ const struct regmap_config *config,
+ const struct qcom_clk_ref_desc *descs,
+ size_t num_clk_refs)
+{
+ return -EOPNOTSUPP;
+}
+
+#endif
+
+#endif
--
2.34.1
^ permalink raw reply related
* [PATCH v6 2/8] dt-bindings: clock: qcom,glymur-tcsr: Add mahua support
From: Qiang Yu @ 2026-06-22 5:11 UTC (permalink / raw)
To: Bjorn Andersson, Michael Turquette, Stephen Boyd, Brian Masney,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Taniya Das,
Konrad Dybcio
Cc: linux-arm-msm, linux-clk, devicetree, linux-kernel, Qiang Yu,
krishna.chundru
In-Reply-To: <20260621-tcsr_qref_0622-v6-0-c939c22ded0c@oss.qualcomm.com>
Mahua shares the same QREF TX/RPT/RX component naming as Glymur, but
has a different topology: a single QREF block fed by REFGEN3 only,
rather than the two independent blocks fed by REFGEN3 and REFGEN4 on
Glymur.
Add qcom,mahua-tcsr compatible and document its required supply
properties.
Signed-off-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
---
.../bindings/clock/qcom,glymur-tcsr.yaml | 68 ++++++++++++++++------
1 file changed, 50 insertions(+), 18 deletions(-)
diff --git a/Documentation/devicetree/bindings/clock/qcom,glymur-tcsr.yaml b/Documentation/devicetree/bindings/clock/qcom,glymur-tcsr.yaml
index 16fc6ab87f9b..2b6422627165 100644
--- a/Documentation/devicetree/bindings/clock/qcom,glymur-tcsr.yaml
+++ b/Documentation/devicetree/bindings/clock/qcom,glymur-tcsr.yaml
@@ -20,7 +20,9 @@ description: |
properties:
compatible:
items:
- - const: qcom,glymur-tcsr
+ - enum:
+ - qcom,glymur-tcsr
+ - qcom,mahua-tcsr
- const: syscon
clocks:
@@ -41,9 +43,11 @@ properties:
vdda-qrefrpt2-0p9-supply: true
vdda-qrefrpt3-0p9-supply: true
vdda-qrefrpt4-0p9-supply: true
+ vdda-qrefrpt5-0p9-supply: true
vdda-qrefrx0-0p9-supply: true
vdda-qrefrx1-0p9-supply: true
vdda-qrefrx2-0p9-supply: true
+ vdda-qrefrx3-0p9-supply: true
vdda-qrefrx4-0p9-supply: true
vdda-qrefrx5-0p9-supply: true
vdda-qreftx0-0p9-supply: true
@@ -54,26 +58,54 @@ properties:
vdda-refgen4-0p9-supply: true
vdda-refgen4-1p2-supply: true
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: qcom,glymur-tcsr
+ then:
+ required:
+ - vdda-qrefrpt0-0p9-supply
+ - vdda-qrefrpt1-0p9-supply
+ - vdda-qrefrpt2-0p9-supply
+ - vdda-qrefrpt3-0p9-supply
+ - vdda-qrefrpt4-0p9-supply
+ - vdda-qrefrx0-0p9-supply
+ - vdda-qrefrx1-0p9-supply
+ - vdda-qrefrx2-0p9-supply
+ - vdda-qrefrx4-0p9-supply
+ - vdda-qrefrx5-0p9-supply
+ - vdda-qreftx0-0p9-supply
+ - vdda-qreftx0-1p2-supply
+ - vdda-qreftx1-0p9-supply
+ - vdda-refgen3-0p9-supply
+ - vdda-refgen3-1p2-supply
+ - vdda-refgen4-0p9-supply
+ - vdda-refgen4-1p2-supply
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: qcom,mahua-tcsr
+ then:
+ required:
+ - vdda-qrefrpt0-0p9-supply
+ - vdda-qrefrpt1-0p9-supply
+ - vdda-qrefrpt2-0p9-supply
+ - vdda-qrefrpt3-0p9-supply
+ - vdda-qrefrpt4-0p9-supply
+ - vdda-qrefrpt5-0p9-supply
+ - vdda-qrefrx1-0p9-supply
+ - vdda-qrefrx2-0p9-supply
+ - vdda-qrefrx3-0p9-supply
+ - vdda-qreftx1-0p9-supply
+ - vdda-refgen3-0p9-supply
+ - vdda-refgen3-1p2-supply
+
required:
- compatible
- clocks
- - vdda-qrefrpt0-0p9-supply
- - vdda-qrefrpt1-0p9-supply
- - vdda-qrefrpt2-0p9-supply
- - vdda-qrefrpt3-0p9-supply
- - vdda-qrefrpt4-0p9-supply
- - vdda-qrefrx0-0p9-supply
- - vdda-qrefrx1-0p9-supply
- - vdda-qrefrx2-0p9-supply
- - vdda-qrefrx4-0p9-supply
- - vdda-qrefrx5-0p9-supply
- - vdda-qreftx0-0p9-supply
- - vdda-qreftx0-1p2-supply
- - vdda-qreftx1-0p9-supply
- - vdda-refgen3-0p9-supply
- - vdda-refgen3-1p2-supply
- - vdda-refgen4-0p9-supply
- - vdda-refgen4-1p2-supply
additionalProperties: false
--
2.34.1
^ permalink raw reply related
* [PATCH v6 1/8] dt-bindings: clock: qcom: Move glymur TCSR to own binding
From: Qiang Yu @ 2026-06-22 5:11 UTC (permalink / raw)
To: Bjorn Andersson, Michael Turquette, Stephen Boyd, Brian Masney,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Taniya Das,
Konrad Dybcio
Cc: linux-arm-msm, linux-clk, devicetree, linux-kernel, Qiang Yu,
krishna.chundru
In-Reply-To: <20260621-tcsr_qref_0622-v6-0-c939c22ded0c@oss.qualcomm.com>
The QREF block supplies reference clocks to PCIe PHYs and requires
dedicated LDO supplies to operate. The digital control interface for QREF
(clkref_en registers) resides in TCSR on glymur. Since QREF has no
dedicated DT node of its own, these supply properties are placed in the
TCSR node which acts as the control interface for QREF.
Add a dedicated binding file for qcom,glymur-tcsr and document the supply
properties.
Mark the relevant supplies as required per compatible using allOf/if/then
conditionals.
Signed-off-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
---
.../bindings/clock/qcom,glymur-tcsr.yaml | 114 +++++++++++++++++++++
.../bindings/clock/qcom,sm8550-tcsr.yaml | 2 -
2 files changed, 114 insertions(+), 2 deletions(-)
diff --git a/Documentation/devicetree/bindings/clock/qcom,glymur-tcsr.yaml b/Documentation/devicetree/bindings/clock/qcom,glymur-tcsr.yaml
new file mode 100644
index 000000000000..16fc6ab87f9b
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/qcom,glymur-tcsr.yaml
@@ -0,0 +1,114 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/qcom,glymur-tcsr.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm TCSR Clock Controller on Glymur
+
+maintainers:
+ - Bjorn Andersson <andersson@kernel.org>
+ - Taniya Das <taniya.das@oss.qualcomm.com>
+
+description: |
+ Qualcomm TCSR clock control module provides the clocks, resets and
+ power domains on Glymur
+
+ See also:
+ - include/dt-bindings/clock/qcom,glymur-tcsr.h
+
+properties:
+ compatible:
+ items:
+ - const: qcom,glymur-tcsr
+ - const: syscon
+
+ clocks:
+ items:
+ - description: TCXO pad clock
+
+ reg:
+ maxItems: 1
+
+ '#clock-cells':
+ const: 1
+
+ '#reset-cells':
+ const: 1
+
+ vdda-qrefrpt0-0p9-supply: true
+ vdda-qrefrpt1-0p9-supply: true
+ vdda-qrefrpt2-0p9-supply: true
+ vdda-qrefrpt3-0p9-supply: true
+ vdda-qrefrpt4-0p9-supply: true
+ vdda-qrefrx0-0p9-supply: true
+ vdda-qrefrx1-0p9-supply: true
+ vdda-qrefrx2-0p9-supply: true
+ vdda-qrefrx4-0p9-supply: true
+ vdda-qrefrx5-0p9-supply: true
+ vdda-qreftx0-0p9-supply: true
+ vdda-qreftx0-1p2-supply: true
+ vdda-qreftx1-0p9-supply: true
+ vdda-refgen3-0p9-supply: true
+ vdda-refgen3-1p2-supply: true
+ vdda-refgen4-0p9-supply: true
+ vdda-refgen4-1p2-supply: true
+
+required:
+ - compatible
+ - clocks
+ - vdda-qrefrpt0-0p9-supply
+ - vdda-qrefrpt1-0p9-supply
+ - vdda-qrefrpt2-0p9-supply
+ - vdda-qrefrpt3-0p9-supply
+ - vdda-qrefrpt4-0p9-supply
+ - vdda-qrefrx0-0p9-supply
+ - vdda-qrefrx1-0p9-supply
+ - vdda-qrefrx2-0p9-supply
+ - vdda-qrefrx4-0p9-supply
+ - vdda-qrefrx5-0p9-supply
+ - vdda-qreftx0-0p9-supply
+ - vdda-qreftx0-1p2-supply
+ - vdda-qreftx1-0p9-supply
+ - vdda-refgen3-0p9-supply
+ - vdda-refgen3-1p2-supply
+ - vdda-refgen4-0p9-supply
+ - vdda-refgen4-1p2-supply
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/qcom,rpmh.h>
+
+ soc {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ clock-controller@1fd5000 {
+ compatible = "qcom,glymur-tcsr", "syscon";
+ reg = <0x0 0x1fd5000 0x0 0x21000>;
+ clocks = <&rpmhcc RPMH_CXO_CLK>;
+ #clock-cells = <1>;
+ #reset-cells = <1>;
+ vdda-qrefrpt0-0p9-supply = <&vreg_l1a>;
+ vdda-qrefrpt1-0p9-supply = <&vreg_l1a>;
+ vdda-qrefrpt2-0p9-supply = <&vreg_l1a>;
+ vdda-qrefrpt3-0p9-supply = <&vreg_l1a>;
+ vdda-qrefrpt4-0p9-supply = <&vreg_l1a>;
+ vdda-qrefrx0-0p9-supply = <&vreg_l1a>;
+ vdda-qrefrx1-0p9-supply = <&vreg_l1a>;
+ vdda-qrefrx2-0p9-supply = <&vreg_l1a>;
+ vdda-qrefrx4-0p9-supply = <&vreg_l1a>;
+ vdda-qrefrx5-0p9-supply = <&vreg_l1a>;
+ vdda-qreftx0-0p9-supply = <&vreg_l1a>;
+ vdda-qreftx0-1p2-supply = <&vreg_l2a>;
+ vdda-qreftx1-0p9-supply = <&vreg_l1a>;
+ vdda-refgen3-0p9-supply = <&vreg_l1a>;
+ vdda-refgen3-1p2-supply = <&vreg_l2a>;
+ vdda-refgen4-0p9-supply = <&vreg_l1a>;
+ vdda-refgen4-1p2-supply = <&vreg_l2a>;
+ };
+ };
+
+...
diff --git a/Documentation/devicetree/bindings/clock/qcom,sm8550-tcsr.yaml b/Documentation/devicetree/bindings/clock/qcom,sm8550-tcsr.yaml
index 08824f848973..19ae0634b922 100644
--- a/Documentation/devicetree/bindings/clock/qcom,sm8550-tcsr.yaml
+++ b/Documentation/devicetree/bindings/clock/qcom,sm8550-tcsr.yaml
@@ -16,7 +16,6 @@ description: |
See also:
- include/dt-bindings/clock/qcom,eliza-tcsr.h
- - include/dt-bindings/clock/qcom,glymur-tcsr.h
- include/dt-bindings/clock/qcom,hawi-tcsrcc.h
- include/dt-bindings/clock/qcom,nord-tcsrcc.h
- include/dt-bindings/clock/qcom,sm8550-tcsr.h
@@ -28,7 +27,6 @@ properties:
items:
- enum:
- qcom,eliza-tcsr
- - qcom,glymur-tcsr
- qcom,hawi-tcsrcc
- qcom,kaanapali-tcsr
- qcom,milos-tcsr
--
2.34.1
^ permalink raw reply related
* [PATCH v6 0/8] clk: qcom: Add common clkref support and migrate Glymur and Mahua
From: Qiang Yu @ 2026-06-22 5:11 UTC (permalink / raw)
To: Bjorn Andersson, Michael Turquette, Stephen Boyd, Brian Masney,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Taniya Das,
Konrad Dybcio
Cc: linux-arm-msm, linux-clk, devicetree, linux-kernel, Qiang Yu,
krishna.chundru, Konrad Dybcio
This series adds a common clkref_en implementation and converts glymur
and mahua to use it, along with the related binding and DTS updates.
The PCIe clkref clocks on Glymur and Mahua gate the QREF block which
provides reference clocks to the PCIe PHYs. QREF requires LDO supplies
and a reference voltage from the refgen block to operate. The refgen
block itself requires vdda-refgen_0p9 and vdda-refgen_1p2 LDOs to
function.
Previously, these QREF votes were done in PHY drivers. In earlier
discussion [1], the feedback was that this is the wrong ownership point:
those supplies are for the QREF controlled by clkref registers, not for
the PHY directly. Based on that feedback, this series keeps the
regulator handling with the clkref control path.
Another reason for this series is reuse. clkref_en registers may live in
different blocks across platforms (for example TCSR on Glymur, TLMM on
SM8750 [2]), while the behavior is the same. The common helper lets each
driver provide simple descriptors (name, offset, optional supplies) and
reuse shared registration and runtime logic.
Glymur and Mahua share the same QREF TX/RPT/RX component naming but
have different PCIe QREF topologies. Both are handled in tcsrcc-glymur.c
via match_data to select the correct descriptor table per compatible.
[1] https://lore.kernel.org/lkml/aEBfV2M-ZqDF7aRz@hovoldconsulting.com/
[2] https://lore.kernel.org/linux-arm-msm/20260202-topic-8750_tcsr-v1-0-cd7e6648c64f@oss.qualcomm.com/
Changes in v6:
- Split dt-bindings patch into two: one to move glymur-tcsr to its own
binding file, and one to add mahua support
- Use regmap_set_bits()/regmap_clear_bits() instead of regmap_update_bits()
in clk-ref.c
- Move clk_init_data from struct qcom_clk_ref to a stack variable in
qcom_clk_ref_register()
- Add Co-developed-by/Reviewed-by tags from Konrad Dybcio
- Add missing regulator supplies for EDP and USB clkref_en on glymur
- Link to v5: https://patch.msgid.link/20260602-tcsr_qref_0527-v5-0-8ea174a59d7e@oss.qualcomm.com
Changes in v5:
- Return 0 if regmap_read fail
- Add a separate file for glymur-tcsr and mahua-tcsr
- Link to v4: https://patch.msgid.link/20260527-tcsr_qref_0527-v4-0-ded83866c9d9@oss.qualcomm.com
Changes in v4:
- Add mahua QREF support (binding, driver, DTS) to avoid dtb check error
- Override pcie5_phy ref clock to RPMH_CXO_CLK on mahua since
TCSR_PCIE_1_CLKREF_EN is not available
- Rename regulator arrays to topology-based names and merge duplicates
- Remove else: false blocks from binding
- Sort supply properties alphabetically in binding and DTS
- Link to v3: https://lore.kernel.org/all/20260506-qref_vote_0506-v3-0-5ab71d2e6f16@oss.qualcomm.com/
Changes in v3:
- Fix dtb check error: allOf:0: 'then' is a dependency of 'if'.
- Link to v2: https://lore.kernel.org/all/20260420-vote_qref_in_tcsrcc-v2-0-589a23ae640a@oss.qualcomm.com/
Changes in v2:
- RFC tag dropped
- Changed back to additionalProperties: false
- Moved all Glymur supply properties into top-level properties so they are explicitly defined.
- Link to v1: https://lore.kernel.org/all/20260331-qref_vote-v1-0-3fd7fbf87864@oss.qualcomm.com/
Signed-off-by: Qiang Yu <qiang.yu@oss.qualcomm.com>
---
Qiang Yu (8):
dt-bindings: clock: qcom: Move glymur TCSR to own binding
dt-bindings: clock: qcom,glymur-tcsr: Add mahua support
clk: qcom: Add generic clkref_en support
clk: qcom: tcsrcc-glymur: Add regulator supplies and migrate to clk_ref helper
clk: qcom: tcsrcc-glymur: Add Mahua QREF regulator support
arm64: dts: qcom: glymur: Add QREF regulator supplies to TCSR
arm64: dts: qcom: mahua: Add QREF regulator supplies to TCSR
arm64: dts: qcom: mahua: Switch pcie5_phy ref clock to RPMH_CXO_CLK
.../bindings/clock/qcom,glymur-tcsr.yaml | 146 +++++++
.../bindings/clock/qcom,sm8550-tcsr.yaml | 2 -
arch/arm64/boot/dts/qcom/glymur-crd.dts | 20 +
arch/arm64/boot/dts/qcom/mahua-crd.dts | 15 +
arch/arm64/boot/dts/qcom/mahua.dtsi | 13 +
drivers/clk/qcom/Makefile | 1 +
drivers/clk/qcom/clk-ref.c | 205 ++++++++++
drivers/clk/qcom/tcsrcc-glymur.c | 438 ++++++++++-----------
include/linux/clk/qcom.h | 67 ++++
9 files changed, 668 insertions(+), 239 deletions(-)
---
base-commit: 3ce97bd3c4f18608335e709c24d6a40e7036cab8
change-id: 20260621-tcsr_qref_0622-b091b8a06f49
Best regards,
--
Qiang Yu <qiang.yu@oss.qualcomm.com>
^ permalink raw reply
* RE: [PATCH v4 00/16] spi: cadence-quadspi: add PHY tuning support
From: Mahapatra, Amit Kumar @ 2026-06-22 4:30 UTC (permalink / raw)
To: Santhosh Kumar K, broonie@kernel.org, robh@kernel.org,
krzk+dt@kernel.org, conor+dt@kernel.org,
miquel.raynal@bootlin.com, richard@nod.at, vigneshr@ti.com,
pratyush@kernel.org, mwalle@kernel.org,
takahiro.kuwano@infineon.com
Cc: linux-spi@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-mtd@lists.infradead.org,
praneeth@ti.com, u-kumar1@ti.com, a-dutta@ti.com,
git (AMD-Xilinx), Amit Mohapatra
In-Reply-To: <20260618073725.84733-1-s-k6@ti.com>
AMD General
Hello Santosh,
> -----Original Message-----
> From: Santhosh Kumar K <s-k6@ti.com>
> Sent: Thursday, June 18, 2026 1:07 PM
> To: broonie@kernel.org; robh@kernel.org; krzk+dt@kernel.org;
> conor+dt@kernel.org; miquel.raynal@bootlin.com; richard@nod.at;
> vigneshr@ti.com; pratyush@kernel.org; mwalle@kernel.org;
> takahiro.kuwano@infineon.com
> Cc: linux-spi@vger.kernel.org; devicetree@vger.kernel.org; linux-
> kernel@vger.kernel.org; linux-mtd@lists.infradead.org; praneeth@ti.com; u-
> kumar1@ti.com; a-dutta@ti.com; s-k6@ti.com
> Subject: [PATCH v4 00/16] spi: cadence-quadspi: add PHY tuning support
>
> This series implements PHY tuning support for the Cadence QSPI controller to
> enable reliable high-speed operations. Without PHY tuning, controllers use
> conservative timing that limits performance. PHY tuning calibrates RX/TX delay lines
> to find optimal data capture timing windows, enabling operation up to the controller's
> maximum frequency.
>
> Background:
> High-speed SPI memory controllers require precise timing calibration for reliable
> operation. At higher frequencies, board-to-board variations make fixed timing
> parameters inadequate. The Cadence QSPI controller includes a PHY interface with
> programmable delay lines (0-127 taps) for RX and TX paths, but these require
> runtime calibration to find the valid timing window.
>
> Approach:
> Add SDR/DDR PHY tuning algorithms for the Cadence controller:
>
> SDR Mode Tuning (1D search):
> - Searches for two consecutive valid RX delay windows
> - Selects the larger window and uses its midpoint for maximum margin
> - TX delay fixed at maximum (127) as it's less critical in SDR
>
> DDR Mode Tuning (2D search):
> - Finds RX boundaries (rxlow/rxhigh) using TX window sweeps
> - Finds TX boundaries (txlow/txhigh) at fixed RX positions
> - Defines valid region corners and detects gaps via binary search
> - Applies temperature compensation for optimal point selection
> - Handles single or dual passing regions with different strategies
Thank you for this series. I had a question regarding the Virtual Concat
driver patch series [1]. Now that it has been merged into the kernel and
enables support for multiple flash devices connected in stacked mode-where
each flash device is probed and configured independently-if both flash
parts are required to operate in DDR mode, each device would need to
perform tuning and store its tuning data separately.
Given this, should we consider this use case and adapt the tuning
architecture to support it?
I'd appreciate your thoughts on this.
[1] https://lore.kernel.org/all/20260204-mtd-virt-concat-v17-0-5e98239bb55b@bootlin.com/
Regards,
Amit
>
> Patch description:
> Infrastructure (1-5):
> - Patch 1: Add spi-max-post-config-frequency to describe maximum
> frequency achievable post controller configuration
> - Patch 2: Add spi-phy-pattern-partition phandle for
> NOR flash PHY tuning pattern location
> - Patch 3: Parse spi-max-post-config-frequency in spi.c; adds
> spi_device.post_config_max_speed_hz (0 when not set
> keeping all existing DT fully compatible)
> - Patch 4: Extend spi_mem_adjust_op_freq() with a bypass: if
> op->max_freq equals post_config_max_speed_hz, return
> immediately leaving op->max_freq unchanged. All other
> ops are capped to max_speed_hz
> - Patch 5: Add execute_tuning callback to spi_controller_mem_ops and
> spi_mem_execute_tuning() wrapper in SPI-MEM core
>
> Cadence QSPI Implementation (6-12):
> - Patch 6: Move cqspi_readdata_capture() earlier (preparatory)
> - Patch 7: Add DQS bit to cqspi_readdata_capture() (preparatory)
> - Patch 8: Add complete PHY tuning support: DLL management, pattern
> verification (NOR via spi-phy-pattern-partition phandle,
> NAND via write-to-cache), SDR 1D and DDR 2D search
> algorithms with temperature compensation, AM654-specific
> execute_tuning entry point;
> - Patch 9: Reject 2-byte-address DDR operations via a new
> CQSPI_NO_2BYTE_ADDR_PHY_DDR quirk flag to work around
> AM654 OSPI erratum i2383
> - Patch 10: Refactor direct read path for PHY support (preparatory)
> - Patch 11: Enable PHY for direct reads, split the transfer into an
> unaligned head, a 16-byte-aligned middle section with PHY
> active, and an unaligned tail
> - Patch 12: Enable PHY for indirect writes of at least
> CQSPI_PHY_MIN_INDIRECT_WRITE_LEN bytes
>
> MTD core (13-16):
> - Patch 13: Extract spinand_select_op_variant() into a shared helper
> spinand_op_find_best() with a skip_mask
> - Patch 14: Negotiate optimal PHY operating point before dirmap
> creation
> - Patch 15: Extract spi_nor_spimem_get_read_op() helper (preparatory)
> - Patch 16: Execute PHY tuning in spi_nor_probe() before creating
> dirmaps
>
> Testing:
> This series was tested on TI's
> AM62Ax SK with OSPI NAND flash and
> AM62Px SK with OSPI NOR flash:
>
> Read throughput:
> |----------------------------------------|
> | | non-PHY | PHY |
> |----------------------------------------|
> | OSPI NOR (8D) | 37.5 MB/s | 216 MB/s |
> |----------------------------------------|
> | OSPI NAND (8S) | 9.2 MB/s | 35.1 MB/s |
> |----------------------------------------|
>
> Write throughput:
> |----------------------------------------|
> | | non-PHY | PHY |
> |----------------------------------------|
> | OSPI NAND (8S) | 6 MB/s | 9.2 MB/s |
> |----------------------------------------|
>
> Test log: https://gist.github.com/santhosh21/fe98754e52970287eb9011154100b62d
> Repo: https://github.com/santhosh21/linux/commits/phy_tuning_v4/
>
> Changes in v4:
> - Add spi-max-post-config-frequency instead of extending spi-max-frequency
> to accept an optional second value
> - Replace spi_mem_apply_base_freq_cap() with spi_mem_adjust_op_freq()
> extension
> - For SPI NOR/NAND, execute PHY tuning before the dirmap creation
> - For SPI NAND, execute PHY tuning across all operation variants available,
> perform duration comparison, and select the best resulting variant
> by taking controller-specific restrictions into account
> - Move i2383 check from cqspi_supports_mem_op() to
> cqspi_am654_ospi_execute_tuning()
> - Rename cdns,phy-pattern-partition to spi-phy-pattern-partition,
> cqspi_phy_enable to cqspi_tune_phy and f_pdata->use_phy to use_tuned_phy
> - Remove redundant spi-max-frequency parsing in driver cqspi_of_get_flash_pdata()
> - Extract DMA refactoring into a preparatory patch
> - Rebase on v7.1
> - Collect tags from Miquel
> - Link to v3: https://lore.kernel.org/linux-spi/20260527175527.2247679-1-s-
> k6@ti.com/
>
> Changes in v3:
> - Drop spi-has-dqs DT property; DQS is now enabled automatically when
> the selected read operation uses DDR signalling (dtr flags in the op)
> - Extend spi-max-frequency to accept an optional second value forming a
> [base-freq, max-freq] pair; the presence of two values signals PHY
> tuning intent and encodes both the conservative base speed and the
> calibration target in one property
> - Add base_speed_hz to struct spi_device (spi.c/spi.h) and parse the
> two-element array there; single-value DT is fully backward-compatible
> - Move frequency enforcement from the cadence driver to core: new
> spi_mem_apply_base_freq_cap() called from spi_mem_exec_op() replaces
> the per-driver cqspi_op_matches_tuned() and non_phy_clk_rate field
> - Propagate the tuned max_freq to dirmap op templates after
> execute_tuning() succeeds; store persistent op templates in
> spi_nor.max_read_op and spinand.{max_read,max_write}_op so the
> frequency writeback survives across the probe call
> - Replace NOR pattern partition lookup by name with a
> cdns,phy-pattern-partition DT phandle pointing directly to the
> partition node
> - Add CQSPI_NO_2BYTE_ADDR_PHY_DDR quirk and reject 2-byte-address DDR
> ops in cqspi_supports_mem_op() to work around AM654 erratum i2383
> - Remove RFC tag
> - Rebase on v7.1-rc5
> - Collect tags from Miquel
> - Link to v2: https://lore.kernel.org/linux-spi/20260113141617.1905039-1-s-
> k6@ti.com/
>
> Changes in v2:
> - Restructure the .execute_tuning() call from spi-mem clients instead
> of mtdcore with best read_op and write_op (optional) passed
> - Add compatible-specific .execute_tuning() call which can be called by
> spi_mem_execute_tuning() if exists
> - Handle tuning requirement check by controller instead of spi-mem
> clients
> - Add support to write the phy_pattern to cache if relevant write_op
> is passed or get the partition offset which contains the phy_pattern
> - Add tuning algorithm for DDR mode
> - Add support for DQS
> - Restrict PHY frequency to tuned operations
> - Link to v1: https://lore.kernel.org/linux-spi/20250811193219.731851-1-s-
> k6@ti.com/
>
> Signed-off-by: Santhosh Kumar K <s-k6@ti.com>
>
> Pratyush Yadav (1):
> mtd: spi-nor: extract read op template construction into helper
>
> Santhosh Kumar K (15):
> spi: dt-bindings: add spi-max-post-config-frequency property
> spi: dt-bindings: add spi-phy-pattern-partition property
> spi: parse spi-max-post-config-frequency into post_config_max_speed_hz
> spi: spi-mem: teach spi_mem_adjust_op_freq() about post-config ops
> spi: spi-mem: add execute_tuning callback and spi_mem_execute_tuning()
> spi: cadence-quadspi: move cqspi_readdata_capture earlier
> spi: cadence-quadspi: add DQS support to read data capture
> spi: cadence-quadspi: add PHY tuning support
> spi: cadence-quadspi: skip DDR PHY tuning for 2-byte-address ops
> (i2383)
> spi: cadence-quadspi: refactor direct read path for PHY support
> spi: cadence-quadspi: enable PHY for direct reads
> spi: cadence-quadspi: enable PHY for indirect writes
> mtd: spinand: extract variant ranking logic into
> spinand_op_find_best()
> mtd: spinand: negotiate optimal PHY operating point before dirmap
> creation
> mtd: spi-nor: run PHY tuning after init and update dirmap frequency
>
> .../bindings/spi/cdns,qspi-nor.yaml | 19 +
> .../bindings/spi/spi-peripheral-props.yaml | 13 +
> drivers/mtd/nand/spi/core.c | 246 +-
> drivers/mtd/spi-nor/core.c | 80 +-
> drivers/spi/spi-cadence-quadspi.c | 2265 +++++++++++++++--
> drivers/spi/spi-mem.c | 40 +
> drivers/spi/spi.c | 2 +
> include/linux/mtd/spi-nor.h | 3 +
> include/linux/mtd/spinand.h | 11 +
> include/linux/spi/spi-mem.h | 14 +
> include/linux/spi/spi.h | 3 +
> 11 files changed, 2493 insertions(+), 203 deletions(-)
>
> --
> 2.34.1
^ permalink raw reply
* Re: [PATCH v3] dt-bindings: misc: add binding for Xilinx AXI-Stream FIFO
From: Aditya Chari S @ 2026-06-22 4:21 UTC (permalink / raw)
To: Krzysztof Kozlowski, robh, krzk+dt, conor+dt, gregkh
Cc: jacobsfeder, devicetree, linux-staging, linux-kernel,
michal.simek, lucas.fariamo08
In-Reply-To: <548d66ce-d01e-4c6a-a77e-7d61378a1c74@kernel.org>
Sorry about the pace - jumped from automated lint feedback straight to
a new version without waiting for an actual reviewer. Won't happen
again.
On the staging binding question - I found the driver-removal thread
from June 2 and read through it. Michal Simek's reply makes clear this
driver isn't going anywhere (their networking team has plans that
depend on it), and that there's already an in-flight binding
conversion from Lucas Faria Mendes that he's actively tracking. I
wasn't aware of that series when I started this.
Given that, I'll withdraw mine rather than duplicate work that's
already further along and already has the relevant maintainer's eyes
on it.
Thanks for pointing me toward the history.
Regards,
Aditya
^ permalink raw reply
* Re: [PATCH v6 2/2] arm64: dts: ti: Add audio overlay for k3-j721s2-evm
From: Wang, Sen @ 2026-06-22 3:52 UTC (permalink / raw)
To: Shah, Moteen, krzk+dt@kernel.org, robh@kernel.org,
conor+dt@kernel.org, Menon, Nishanth, Raghavendra, Vignesh,
kristo@kernel.org
Cc: devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, Kumar, Udit,
Gujulan Elango, Hari Prasath, Abhilash Chandra, Yemike
In-Reply-To: <20260619062749.1575066-3-m-shah@ti.com>
On 6/19/2026 1:27 AM, Shah, Moteen wrote:
> From: Jayesh Choudhary <j-choudhary@ti.com>
>
> Add device tree overlay to enable analog audio support on J721S2-EVM
> using PCM3168A codec connected to McASP4 serializers.
>
> - Add audio_refclk1 clock node to k3-j721s2-main.dtsi
> - Add nodes for sound-card, audio codec, I2C3 and McASP4
> - Add pinmux for I2C3, McASP4, AUDIO_EXT_REFCLK1 and WKUP_GPIO_0
> - Add GPIO expander (TCA6408) for codec control
> - Add GPIO hogs to route I2C3 lines and McASP serializers
> - Set idle-state to 0 in mux0 and mux1 for McASP signal routing
>
Hi Moteen, thanks for the patch.
Reviewed-by: Sen Wang <sen@ti.com>
^ permalink raw reply
* RE: [PATCH 3/8] Bluetooth: btnxpuart: Add M.2 Bluetooth device support using pwrseq
From: Sherry Sun @ 2026-06-22 3:51 UTC (permalink / raw)
To: Frank Li (OSS), Sherry Sun (OSS)
Cc: robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
Frank Li, s.hauer@pengutronix.de, kernel@pengutronix.de,
festevam@gmail.com, Amitkumar Karwar, Neeraj Sanjay Kale,
marcel@holtmann.org, luiz.dentz@gmail.com, Hongxing Zhu,
l.stach@pengutronix.de, lpieralisi@kernel.org,
kwilczynski@kernel.org, mani@kernel.org, bhelgaas@google.com,
brgl@kernel.org, imx@lists.linux.dev, linux-pci@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-bluetooth@vger.kernel.org,
linux-pm@vger.kernel.org
In-Reply-To: <ajQ4oBUNGOrhcPX5@SMW015318>
> On Thu, Jun 18, 2026 at 06:10:42PM +0800, Sherry Sun (OSS) wrote:
> > From: Sherry Sun <sherry.sun@nxp.com>
> >
> > Power supply to the M.2 Bluetooth device attached to the host using
> > M.2 connector is controlled using the 'uart' pwrseq device. So add
> > support for getting the pwrseq device if the OF graph link is present.
> > Once obtained, the existing pwrseq APIs can be used to control the
> > power supplies of the
> > M.2 card.
> >
> > Signed-off-by: Sherry Sun <sherry.sun@nxp.com>
> > ---
> > drivers/bluetooth/btnxpuart.c | 33 ++++++++++++++++++++++++++++++---
> > 1 file changed, 30 insertions(+), 3 deletions(-)
> >
> > diff --git a/drivers/bluetooth/btnxpuart.c
> > b/drivers/bluetooth/btnxpuart.c index e7036a48ce48..1aa8972f0dab
> > 100644
> > --- a/drivers/bluetooth/btnxpuart.c
> > +++ b/drivers/bluetooth/btnxpuart.c
> > @@ -9,6 +9,8 @@
> >
> > #include <linux/serdev.h>
> > #include <linux/of.h>
> > +#include <linux/of_graph.h>
> > +#include <linux/pwrseq/consumer.h>
> > #include <linux/skbuff.h>
> > #include <linux/unaligned.h>
> > #include <linux/firmware.h>
> > @@ -211,6 +213,7 @@ struct btnxpuart_dev {
> >
> > struct ps_data psdata;
> > struct btnxpuart_data *nxp_data;
> > + struct pwrseq_desc *pwrseq;
> > struct reset_control *pdn;
> > struct hci_uart hu;
> > };
> > @@ -1866,11 +1869,27 @@ static int nxp_serdev_probe(struct
> serdev_device *serdev)
> > return err;
> > }
> >
> > + if (of_graph_is_present(dev_of_node(&serdev->ctrl->dev))) {
> > + struct pwrseq_desc *pwrseq;
> > +
> > + pwrseq = devm_pwrseq_get(&serdev->ctrl->dev, "uart");
> > + if (IS_ERR(pwrseq))
> > + return PTR_ERR(pwrseq);
> > +
> > + nxpdev->pwrseq = pwrseq;
> > + err = pwrseq_power_on(pwrseq);
> > + if (err) {
> > + dev_err(&serdev->dev, "Failed to power on
> pwrseq\n");
> > + return err;
> > + }
>
> Can you provide helper function like devm clk get and enabled?
> like devm_pwrsq_get_on()
>
> So simple below error handle.
Ok, will try.
Best Regards
Sherry
>
> > + }
> > +
> > /* Initialize and register HCI device */
> > hdev = hci_alloc_dev();
> > if (!hdev) {
> > dev_err(&serdev->dev, "Can't allocate HCI device\n");
> > - return -ENOMEM;
> > + err = -ENOMEM;
> > + goto err_pwrseq_power_off;
> > }
> >
> > reset_control_deassert(nxpdev->pdn);
> > @@ -1903,11 +1922,14 @@ static int nxp_serdev_probe(struct
> > serdev_device *serdev)
> >
> > if (hci_register_dev(hdev) < 0) {
> > dev_err(&serdev->dev, "Can't register HCI device\n");
> > + err = -ENODEV;
> > goto probe_fail;
> > }
> >
> > - if (ps_setup(hdev))
> > + if (ps_setup(hdev)) {
> > + err = -ENODEV;
> > goto probe_fail;
> > + }
> >
> > hci_devcd_register(hdev, nxp_coredump, nxp_coredump_hdr,
> > nxp_coredump_notify);
> > @@ -1917,7 +1939,10 @@ static int nxp_serdev_probe(struct
> > serdev_device *serdev)
> > probe_fail:
> > reset_control_assert(nxpdev->pdn);
> > hci_free_dev(hdev);
> > - return -ENODEV;
> > +err_pwrseq_power_off:
> > + if (nxpdev->pwrseq)
> > + pwrseq_power_off(nxpdev->pwrseq);
> > + return err;
> > }
> >
> > static void nxp_serdev_remove(struct serdev_device *serdev) @@
> > -1944,6 +1969,8 @@ static void nxp_serdev_remove(struct serdev_device
> *serdev)
> > ps_cleanup(nxpdev);
> > hci_unregister_dev(hdev);
> > reset_control_assert(nxpdev->pdn);
> > + if (nxpdev->pwrseq)
> > + pwrseq_power_off(nxpdev->pwrseq);
> > hci_free_dev(hdev);
> > }
> >
> > --
> > 2.50.1
> >
> >
^ permalink raw reply
* RE: [PATCH 0/8] Add PCIe M.2 Key E connector support for NXP i.MX boards
From: Sherry Sun @ 2026-06-22 3:18 UTC (permalink / raw)
To: Bartosz Golaszewski, Sherry Sun (OSS)
Cc: imx@lists.linux.dev, linux-pci@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-bluetooth@vger.kernel.org,
linux-pm@vger.kernel.org, robh@kernel.org, krzk+dt@kernel.org,
conor+dt@kernel.org, Frank Li, s.hauer@pengutronix.de,
kernel@pengutronix.de, festevam@gmail.com, Amitkumar Karwar,
Neeraj Sanjay Kale, marcel@holtmann.org, luiz.dentz@gmail.com,
Hongxing Zhu, l.stach@pengutronix.de, lpieralisi@kernel.org,
kwilczynski@kernel.org, mani@kernel.org, bhelgaas@google.com
In-Reply-To: <CAMRc=MfsNa4itdpyGtR16wMb+wMkJwg+9=QJF2-oOoVVfFCF3g@mail.gmail.com>
> On Thu, 18 Jun 2026 12:10:39 +0200, "Sherry Sun (OSS)"
> <sherry.sun@oss.nxp.com> said:
> > From: Sherry Sun <sherry.sun@nxp.com>
> >
> > This series adds support for NXP Wi-Fi/BT combo chips (88W9098, AW693)
> > inserted into PCIe M.2 Key E connectors on several i.MX EVK/MEK boards.
> >
> > For M.2 cards that rely on PCIe L2 link state and wake-up mechanisms,
> > the card must remain powered during suspend. Patch 1 uses the existing
> > dw_pcie_rp::skip_pwrctrl_off flag to skip power-off during suspend and
> > skip power-on during the init path.
> >
> > Alsp the btnxpuart driver is extended to obtain a pwrseq descriptor
> > via the OF graph on the UART controller device in patch 2.
> >
> > Sherry Sun (8):
> > PCI: imx6: Add skip_pwrctrl_off flag support
> > power: sequencing: pcie-m2: Add PCI ID for NXP 88W9098 and AW693
> > Bluetooth
>
> Can this be applied independently without build-time issues?
Hi Bart,
Yes, this patch can be applied independently, I was able to successfully
build it based on the following base-commit:
3ce97bd3c4f18608335e709c24d6a40e7036cab8.
However, please note that it may conflict with the following patch when
applied: https://lore.kernel.org/all/20260617143055.820096-1-wei.deng@oss.qualcomm.com/.
Best Regards
Sherry
>
> > Bluetooth: btnxpuart: Add M.2 Bluetooth device support using pwrseq
> > arm64: dts: imx8mq-evk: Describe the PCIe M.2 Key E connector
> > arm64: dts: imx95-19x19-evk: Describe the PCIe M.2 Key E connector
> > arm64: dts: imx8dxl-evk: Describe the PCIe M.2 Key E connector
> > arm64: dts: imx8qm-mek: Describe the PCIe M.2 Key E connector
> > arm64: dts: imx8qxp-mek: Describe the PCIe M.2 Key E connector
> >
> > arch/arm64/boot/dts/freescale/imx8dxl-evk.dts | 56 +++++++++++++-----
> > arch/arm64/boot/dts/freescale/imx8mq-evk.dts | 44 ++++++++++++--
> > arch/arm64/boot/dts/freescale/imx8qm-mek.dts | 58 ++++++++++++++-----
> > arch/arm64/boot/dts/freescale/imx8qxp-mek.dts | 54 ++++++++++++-----
> > .../boot/dts/freescale/imx95-19x19-evk.dts | 55 +++++++++++++-----
> > drivers/bluetooth/btnxpuart.c | 33 ++++++++++-
> > drivers/pci/controller/dwc/pci-imx6.c | 36 +++++++-----
> > drivers/power/sequencing/pwrseq-pcie-m2.c | 4 ++
> > 8 files changed, 264 insertions(+), 76 deletions(-)
> >
> > --
> > 2.50.1
> >
> >
^ permalink raw reply
* Re: [PATCH v5 2/4] clk: cix: add sky1 audss clock controller
From: sashiko-bot @ 2026-06-22 2:40 UTC (permalink / raw)
To: joakim.zhang; +Cc: conor+dt, devicetree, robh
In-Reply-To: <20260622022520.3127103-3-joakim.zhang@cixtech.com>
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] Clock operations access MMIO registers without waking the device from runtime suspend, causing fatal bus errors.
- [High] Error paths in probe and runtime resume leak the PM usage counter, leave hardware out of reset, and leak clocks if CONFIG_PM is disabled.
- [High] Mixing devres clock provider registration with manual composite clock cleanup creates a Use-After-Free window.
- [High] Instance-specific register state is saved into a global static array during runtime suspend, causing state corruption.
- [High] Unbalanced `pm_runtime_disable` depth in `sky1_audss_clk_remove` causes driver rebind failures.
- [Low] Unconditional assignment of `dev_pm_ops` structure pointer without the `pm_ptr()` wrapper.
--
commit 56f48f8a9fbe140aaac12e836bfe295a80d9790a
Author: Joakim Zhang <joakim.zhang@cixtech.com>
clk: cix: add sky1 audss clock controller
Add a platform driver for the Cix Sky1 AUDSS CRU. The driver maps
the CRU registers and registers mux, divider and gate clocks for
DSP, SRAM, HDA, DMAC, I2S, mailbox, watchdog and timer blocks.
Four SoC-level audio reference clocks are enabled as inputs to the
internal clock tree. The driver releases the AUDSS NOC reset, enables
runtime PM and instantiates the auxiliary reset device.
> diff --git a/drivers/clk/cix/clk-sky1-audss.c b/drivers/clk/cix/clk-sky1-audss.c
> new file mode 100644
> index 0000000000000..8a4ab2d9419cf
> --- /dev/null
> +++ b/drivers/clk/cix/clk-sky1-audss.c
> @@ -0,0 +1,1201 @@
[ ... ]
> +static u32 sky1_reg_save[][2] = {
> + { INFO_HIFI0, 0 },
> + { INFO_CLK_GATE, 0 },
> + { INFO_CLK_DIV, 0 },
> + { INFO_CLK_MUX, 0 },
> + { INFO_MCLK, 0 },
> +};
[ ... ]
> +static int sky1_audss_clk_mux_set_parent(struct clk_hw *hw, u8 index)
> +{
> + struct clk_mux *mux = to_clk_mux(hw);
> + u32 val = clk_mux_index_to_val(mux->table, mux->flags, index);
> + struct sky1_clk_mux *sky1_mux = to_sky1_clk_mux(mux);
> + unsigned long flags = 0;
> + u32 reg;
> +
> + if (mux->lock)
> + spin_lock_irqsave(mux->lock, flags);
> + else
> + __acquire(mux->lock);
> +
> + if (mux->flags & CLK_MUX_HIWORD_MASK) {
> + reg = mux->mask << (mux->shift + 16);
> + } else {
> + regmap_read(sky1_mux->regmap, sky1_mux->offset, ®);
[Severity: High]
Is it safe to access the MMIO registers directly in the CCF clock operations
without first requesting a PM runtime wake?
The probe function puts the device into runtime suspend at the end of
execution. Since the Common Clock Framework does not automatically wake
provider devices for operations like set_parent or recalc_rate, could
accessing the registers of a powered-off hardware block trigger a
synchronous external abort?
[ ... ]
> +static int sky1_audss_clk_probe(struct platform_device *pdev)
> +{
[ ... ]
> + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, priv->clk_data);
> + if (ret) {
> + dev_err(dev, "failed to add clock provider: %d\n", ret);
> + goto fail_register;
> + }
> +
> + ret = sky1_audss_reset_controller_register(dev);
> + if (ret)
> + goto fail_register;
> +
> + pm_runtime_put_sync(dev);
> +
> + return 0;
> +
> +fail_register:
> +fail_array_clk:
> + while (i--)
> + clk_hw_unregister_composite(clk_table[devtype_data->clk_cfg[i].id]);
> +fail_fixed_clk:
> +fail_clks_set:
> + pm_runtime_put_sync(dev);
> +err_pm:
> + pm_runtime_disable(dev);
> + return ret;
> +}
[Severity: High]
Do these error paths properly clean up resources?
If initialization fails and jumps to err_pm, it skips calling
pm_runtime_put_noidle() and reset_control_assert(), which appears to
permanently leak the PM usage counter and leave the hardware out of reset.
Additionally, if it fails and branches to fail_clks_set, it relies on
pm_runtime_put_sync(). If CONFIG_PM is disabled, will this leave the
clocks permanently enabled?
[ ... ]
> +static void sky1_audss_clk_remove(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct sky1_audss_clks_priv *priv = dev_get_drvdata(dev);
> + const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data;
> + int i = 0;
> +
> + for (i = 0; i < devtype_data->clk_cfg_size; i++)
> + clk_hw_unregister_composite(priv->clk_data->hws[devtype_data->clk_cfg[i].id]);
[Severity: High]
Does mixing devm_of_clk_add_hw_provider() with manual unregistration
create a use-after-free window?
Since devres executes after sky1_audss_clk_remove() returns, the OF clock
provider will remain active while the composite clock memory has already
been freed. Could a concurrent clk_get() lookup during this window return
a pointer to freed clk_hw memory?
> +
> + if (!pm_runtime_status_suspended(dev))
> + pm_runtime_force_suspend(dev);
> +
> + pm_runtime_disable(dev);
> +}
[Severity: High]
Could this sequence cause driver rebind failures?
The pm_runtime_force_suspend() function internally increments the disable
depth. Calling pm_runtime_disable() immediately after increments it a
second time. Since probe() only enables PM once, would unbinding and
rebinding this driver leave the device with a disable_depth of 1,
permanently preventing runtime PM from enabling?
[ ... ]
> +static int __maybe_unused sky1_audss_clk_runtime_suspend(struct device *dev)
> +{
> + struct sky1_audss_clks_priv *priv = dev_get_drvdata(dev);
> + const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data;
> + unsigned long flags;
> + int i;
> +
> + spin_lock_irqsave(&priv->lock, flags);
> + for (i = 0; i < devtype_data->reg_save_size; i++)
> + regmap_read(priv->regmap_cru,
> + devtype_data->reg_save[i][0], &devtype_data->reg_save[i][1]);
> + spin_unlock_irqrestore(&priv->lock, flags);
[Severity: High]
Could this cause data corruption if multiple instances of this device exist?
The sky1_reg_save variable is a global static array, but the register state
is being written directly into it during runtime suspend. If there are dual
audio subsystems, will one instance overwrite the other's saved clock
configurations?
[ ... ]
> +static int __maybe_unused sky1_audss_clk_runtime_resume(struct device *dev)
> +{
> + struct sky1_audss_clks_priv *priv = dev_get_drvdata(dev);
> + const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data;
> + unsigned long flags;
> + int i, ret;
> +
> + ret = reset_control_deassert(priv->rst_noc);
> + if (ret)
> + return ret;
> +
> + ret = sky1_audss_clks_enable(priv);
> + if (ret) {
> + dev_err(dev, "failed to enable clocks\n");
> + return ret;
> + }
[Severity: High]
Does the runtime resume error path miss asserting the reset control?
If sky1_audss_clks_enable() fails, the function returns without calling
reset_control_assert(), leaving the hardware out of reset while the system
considers it suspended.
[ ... ]
> +static struct platform_driver sky1_audss_clk_driver = {
> + .probe = sky1_audss_clk_probe,
> + .remove = sky1_audss_clk_remove,
> + .driver = {
> + .name = "sky1-audss-clk",
> + .suppress_bind_attrs = true,
> + .of_match_table = sky1_audss_clk_of_match,
> + .pm = &sky1_audss_clk_pm_ops,
[Severity: Low]
Is it necessary to wrap the dev_pm_ops pointer assignment with pm_ptr()?
Without the wrapper, will the structure and its referenced callbacks be
compiled into the kernel as dead code when CONFIG_PM is disabled?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260622022520.3127103-1-joakim.zhang@cixtech.com?part=2
^ permalink raw reply
* Re: [PATCH v5 3/4] reset: cix: add sky1 audss auxiliary reset driver
From: sashiko-bot @ 2026-06-22 2:35 UTC (permalink / raw)
To: joakim.zhang; +Cc: conor+dt, devicetree, robh
In-Reply-To: <20260622022520.3127103-4-joakim.zhang@cixtech.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] The reset status callback returns inverted logic compared to the active-low hardware state.
- [High] The `sky1_audss_reset_status()` callback ignores the return value of `regmap_read()`, leading to the use of an uninitialized variable on read failures.
- [High] Hardware update failures are silently ignored in the `assert`, `deassert`, and `reset` callbacks.
- [High] Reset operations access hardware registers without holding a PM runtime reference to the parent device.
--
commit ea91702e5e9b052b10549ef50f1a521b588916ef
Author: Joakim Zhang <joakim.zhang@cixtech.com>
reset: cix: add sky1 audss auxiliary reset driver
Add an auxiliary reset controller driver for the AUDSS CRU. Sixteen
software reset lines for audio subsystem peripherals are controlled
through one register in the CRU register map.
> diff --git a/drivers/reset/reset-sky1-audss.c b/drivers/reset/reset-sky1-audss.c
> new file mode 100644
> index 0000000000000..f29fe3554ec17
> --- /dev/null
> +++ b/drivers/reset/reset-sky1-audss.c
[ ... ]
> +static int sky1_audss_reset_set(struct reset_controller_dev *rcdev,
> + unsigned long id, bool assert)
> +{
> + struct sky1_audss_reset *priv = to_sky1_audss_reset(rcdev);
> + const struct sky1_audss_reset_map *signal = &priv->map[id];
> + unsigned int value = assert ? 0 : signal->mask;
> +
> + return regmap_update_bits(priv->regmap, signal->offset, signal->mask, value);
> +}
[Severity: High]
Does this regmap access need to be protected by holding a runtime PM
reference on the parent device? If the parent device implements runtime PM,
can it suspend and disable clocks while a consumer calls this reset function,
potentially causing a bus hang?
> +
> +static int sky1_audss_reset_assert(struct reset_controller_dev *rcdev,
> + unsigned long id)
> +{
> + sky1_audss_reset_set(rcdev, id, true);
[Severity: High]
Is it intentional to discard the error code returned by sky1_audss_reset_set()?
If the underlying regmap_update_bits() fails, this will unconditionally report
success.
> + usleep_range(SKY1_RESET_SLEEP_MIN_US, SKY1_RESET_SLEEP_MAX_US);
> + return 0;
> +}
> +
> +static int sky1_audss_reset_deassert(struct reset_controller_dev *rcdev,
> + unsigned long id)
> +{
> + sky1_audss_reset_set(rcdev, id, false);
[Severity: High]
Should this also propagate the return value of sky1_audss_reset_set() instead
of ignoring potential hardware update failures?
> + usleep_range(SKY1_RESET_SLEEP_MIN_US, SKY1_RESET_SLEEP_MAX_US);
> + return 0;
> +}
> +
> +static int sky1_audss_reset(struct reset_controller_dev *rcdev,
> + unsigned long id)
> +{
> + sky1_audss_reset_assert(rcdev, id);
> + sky1_audss_reset_deassert(rcdev, id);
[Severity: High]
Does this code need to capture and propagate errors from the assert and
deassert steps? It seems hardware faults might be masked from caller drivers.
> + return 0;
> +}
> +
> +static int sky1_audss_reset_status(struct reset_controller_dev *rcdev,
> + unsigned long id)
> +{
> + struct sky1_audss_reset *priv = to_sky1_audss_reset(rcdev);
> + const struct sky1_audss_reset_map *signal = &priv->map[id];
> + unsigned int value;
> +
> + regmap_read(priv->regmap, signal->offset, &value);
[Severity: High]
If regmap_read() fails, it leaves value uninitialized. Should this check
the return value of regmap_read() and return the error code to avoid using
uninitialized stack memory?
Also, similar to sky1_audss_reset_set(), does this register access need
pm_runtime_get_sync(dev->parent) to prevent reading from a suspended hardware
block?
> + return !!(value & signal->mask);
[Severity: High]
Since sky1_audss_reset_set() writes 0 to assert the reset line, the hardware
appears to use an active-low scheme. However, this returns 0 when the bit is
cleared. The reset controller framework requires the status callback to return
a positive value when asserted. Will this report inverted states to consumer
drivers?
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260622022520.3127103-1-joakim.zhang@cixtech.com?part=3
^ permalink raw reply
* Re: [PATCH v4 4/6] drm/verisilicon: add DC8000 (DCUltraLite) display controller support
From: Joey Lu @ 2026-06-22 2:30 UTC (permalink / raw)
To: Icenowy Zheng, maarten.lankhorst, mripard, tzimmermann, airlied,
simona, robh, krzk+dt, conor+dt, Michael Turquette, Stephen Boyd,
Brian Masney
Cc: ychuang3, schung, yclu4, dri-devel, devicetree, linux-arm-kernel,
linux-kernel, linux-clk
In-Reply-To: <0bb460aefb97e44cc0890a7841b8d217349143de.camel@iscas.ac.cn>
On 6/18/2026 6:33 PM, Icenowy Zheng wrote:
> (CC'ed clk maintainers for weird clock gate bit)
>
> 在 2026-06-17三的 18:35 +0800,Joey Lu写道:
>> On 6/15/2026 4:51 PM, Icenowy Zheng wrote:
>>> 在 2026-06-15一的 14:50 +0800,Joey Lu写道:
>>>> The Nuvoton MA35D1 SoC integrates a Verisilicon DCUltraLite
>>>> display
>>>> controller whose register layout differs from the DC8200 in
>>>> several
>>>> important ways:
>>>>
>>>> 1. No CONFIG_EX commit path: framebuffer updates use the enable
>>>> (bit
>>>> 0)
>>>> and reset (bit 4) bits in FB_CONFIG instead of the DC8200
>>>> staging
>>>> registers (FB_CONFIG_EX, FB_TOP_LEFT, FB_BOTTOM_RIGHT,
>>>> FB_BLEND_CONFIG, PANEL_CONFIG_EX).
>>>>
>>>> 2. No PANEL_START register: panel output starts when
>>>> PANEL_CONFIG.RUNNING is set; there is no multi-display sync
>>>> start
>>>> register.
>>>>
>>>> 3. Different IRQ registers: DCUltraLite uses DISP_IRQ_STA
>>>> (0x147C) /
>>>> DISP_IRQ_EN (0x1480) versus DC8200's TOP_IRQ_ACK (0x0010) /
>>>> TOP_IRQ_EN (0x0014).
>>>>
>>>> 4. Per-frame commit cycle: DCUltraLite requires the VALID bit in
>>>> FB_CONFIG to be set at the start of each atomic commit
>>>> (crtc_begin)
>>>> and cleared after (crtc_flush).
>>>>
>>>> 5. Simpler clock topology: only 'core' (bus gate) and 'pix0'
>>>> (pixel
>>>> divider) clocks; no axi or ahb clocks required. Make axi_clk
>>>> and
>>>> ahb_clk optional (devm_clk_get_optional_enabled) so DC8000
>>>> nodes
>>>> without those clocks are handled gracefully.
>>>>
>>>> Add vs_dc8000.c implementing the vs_dc_funcs vtable for the above
>>>> differences. The probe now selects vs_dc8000_funcs when the
>>>> identified
>>>> generation is VSDC_GEN_DC8000 (DCUltraLite reads model 0x0,
>>>> revision 0x5560, customer_id 0x305).
>>>>
>>>> Signed-off-by: Joey Lu <a0987203069@gmail.com>
>>>> ---
>>>> drivers/gpu/drm/verisilicon/Makefile | 2 +-
>>>> drivers/gpu/drm/verisilicon/vs_dc.c | 9 ++-
>>>> drivers/gpu/drm/verisilicon/vs_dc.h | 1 +
>>>> drivers/gpu/drm/verisilicon/vs_dc8000.c | 78
>>>> +++++++++++++++++++++++++
>>>> 4 files changed, 86 insertions(+), 4 deletions(-)
>>>> create mode 100644 drivers/gpu/drm/verisilicon/vs_dc8000.c
>>>>
>>>> diff --git a/drivers/gpu/drm/verisilicon/Makefile
>>>> b/drivers/gpu/drm/verisilicon/Makefile
>>>> index 9d4cd16452fa..d2fd8e4dff24 100644
>>>> --- a/drivers/gpu/drm/verisilicon/Makefile
>>>> +++ b/drivers/gpu/drm/verisilicon/Makefile
>>>> @@ -1,6 +1,6 @@
>>>> # SPDX-License-Identifier: GPL-2.0-only
>>>>
>>>> -verisilicon-dc-objs := vs_bridge.o vs_crtc.o vs_dc.o vs_dc8200.o
>>>> vs_drm.o vs_hwdb.o \
>>>> +verisilicon-dc-objs := vs_bridge.o vs_crtc.o vs_dc.o vs_dc8200.o
>>>> vs_dc8000.o vs_drm.o vs_hwdb.o \
>>>> vs_plane.o vs_primary_plane.o vs_cursor_plane.o
>>>>
>>>> obj-$(CONFIG_DRM_VERISILICON_DC) += verisilicon-dc.o
>>>> diff --git a/drivers/gpu/drm/verisilicon/vs_dc.c
>>>> b/drivers/gpu/drm/verisilicon/vs_dc.c
>>>> index 9729b693d360..9499fffbca58 100644
>>>> --- a/drivers/gpu/drm/verisilicon/vs_dc.c
>>>> +++ b/drivers/gpu/drm/verisilicon/vs_dc.c
>>>> @@ -90,13 +90,13 @@ static int vs_dc_probe(struct platform_device
>>>> *pdev)
>>>> return PTR_ERR(dc->core_clk);
>>>> }
>>>>
>>>> - dc->axi_clk = devm_clk_get_enabled(dev, "axi");
>>>> + dc->axi_clk = devm_clk_get_optional_enabled(dev, "axi");
>>>> if (IS_ERR(dc->axi_clk)) {
>>>> dev_err(dev, "can't get axi clock\n");
>>>> return PTR_ERR(dc->axi_clk);
>>>> }
>>>>
>>>> - dc->ahb_clk = devm_clk_get_enabled(dev, "ahb");
>>>> + dc->ahb_clk = devm_clk_get_optional_enabled(dev, "ahb");
>>> Please make the clock change a separated patch for atomicity.
>>>
>>> BTW the MA35D1 manual's clock tree shows that DCUltra appears on
>>> AXI2
>>> ACLK, AHB_HCLK2, behind a mux of SYS-PLL/EPLL-DIV2 (which seems to
>>> be
>>> the core clock), and behind a divider (which seems to be the pixel
>>> clock).
>>>
>>> However it's weird that only one DCUltra Clock Enable Bit exists
>>> despite both bus clocks have "ICG" (I think it means "Integrated
>>> Clock
>>> Gating"). In addition the linux clk-ma35d1 driver assigns
>>> "dcu_gate" as
>>> a downstream of "dcu_mux", although the Figure 6.5-2 in the TRM
>>> shows
>>> no ICG after the "Display core CLK" mux.
>>>
>>> Is the two bus clocks controlled by a single gate bit, and is the
>>> bit
>>> also gating DC core clock?
>>>
>>> Thanks,
>>> Icenowy
>> I will split the axi/ahb optional-clock change into its own patch in
>> v5
>> for atomicity.
>> Regarding the MA35D1 clock tree: from the TRM, the single "dcu_gate"
>> bit
>> gates both bus clocks (AXI ACLK and AHB HCLK) together with the
>> display
>> core clock through the same ICG cell. The clk-ma35d1 driver exposes
>> only
>> "dcu_gate" (downstream of "dcu_mux") and does not provide separate
> Then it's one of the case that the clock tree doesn't properly
> represent the hardware, which is bad. However, as three gates share the
> same bit, I am not sure how to represent such kind of thing in the
> common clk framework.
>
>> axi/ahb clock entries. Therefore the MA35D1 DT binding will use only
>> two
>> clocks ("core" and "pix0"); making axi and ahb optional in the driver
>> is the correct approach, and this will be stated clearly in the
>> split-out patch.
> I agree to make them optional, although these two clocks do exist in
> the hardware of MA35D1.
>
> Thanks,
> Icenowy
As mentioned in the DT binding reply, the absence of separate AXI/AHB
clock entries for DCU in clk driver is due to the hardware design
constraint of a single shared enable bit, not a driver oversight. In v5,
the axi and ahb clock fetches in `vs_dc_probe` will be split into their
own patch and made optional via `devm_clk_get_optional_enabled`, with a
comment explaining that on MA35D1 the AXI and AHB bus clocks share the
single `dcu_gate` enable bit and are therefore not separately exposed by
the clock driver.
>>>> if (IS_ERR(dc->ahb_clk)) {
>>>> dev_err(dev, "can't get ahb clock\n");
>>>> return PTR_ERR(dc->ahb_clk);
^ permalink raw reply
* Re: [PATCH 1/4] dt-bindings: input: Add binding for Qualcomm SPMI PMIC haptics
From: Fenglin Wu @ 2026-06-22 2:28 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: linux-arm-msm, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Lee Jones, Stephen Boyd, Bjorn Andersson,
Konrad Dybcio, David Collins, Subbaraman Narayanamurthy,
Kamal Wadhwa, kernel, linux-input, devicetree, linux-kernel
In-Reply-To: <44778bec-68c5-426d-bc05-b4013630b3ee@kernel.org>
On 6/19/2026 12:18 PM, Krzysztof Kozlowski wrote:
> On 17/06/2026 13:02, Fenglin Wu wrote:
>> On 6/17/2026 6:35 PM, Krzysztof Kozlowski wrote:
>>> On Tue, Jun 16, 2026 at 03:08:24AM -0700, Fenglin Wu wrote:
>>>> ....
>>>> +
>>>> + qcom,lra-period-us:
>>>> + description:
>>>> + LRA actuator initial resonance period in microseconds
>>>> + (1,000,000 / resonant_freq_hz). Used to configure T_LRA-based play
>>>> + rates and the auto-resonance zero-crossing window.
>>> This does not feel like static characteristic. Isn't period depending on
>>> intensity of vibration you want to have? Why would that be fixed per
>>> board?
>> This period is specifically used for playbacks that require
>> auto-resonance to be enabled, which I referred to as "T_LRA-based" and
>> "auto-resonance zero-crossing window." It plays a key role in the
>> "DIRECT_PLAY" mode, which produces a constant vibration effect. To
>> adjust the vibration intensity during this constant effect, the hardware
>> does it by scaling the peak voltage of the driver signals, rather than
>> changing the frequency.
> But maybe changing frequency runtime still would be useful?
It could be, but the LRA F0 (resonant frequency) still needs to be the
starting point. You can control vibration intensity by driving the LRA
slightly off resonance by a given percentage—for example, to reach 50%
vibration, you could probably drive it 10% off resonant frequency, and
that mapping also depends on the LRA characteristic. Keep in mind that
LRA is a spring-mass resonant system, so its output is not linear with
driving frequency; it is a High_Q system, and its output actually shows
a sharp peak at the resonance point. By contrast, the relationship
between driving voltage and its output is much more linear, so scaling
vibration intensity by adjusting the driving voltage is easier to
control. Qcom haptics HW scales vibration intensity in DIRECT_PLAY mode
(for constant vibration effect) by scaling the driving voltage instead.
That said, the HW can also change the driving waveform frequency by
updating the T-LRA registers, and this property has to be specified as
an initial value; otherwise, you won't have a baseline to achieve that.
>
> What does the frequency depend on? IOW, how do you choose the value here
> - based on some motor-part characteristics?
It's the resonant frequency of the LRA actuator that's mounted on the board.
>
> Best regards,
> Krzysztof
^ permalink raw reply
* Re: [PATCH v4 1/6] dt-bindings: display: verisilicon, dc: generalize for single-output variants
From: Joey Lu @ 2026-06-22 2:28 UTC (permalink / raw)
To: Icenowy Zheng, maarten.lankhorst, mripard, tzimmermann, airlied,
simona, robh, krzk+dt, conor+dt
Cc: ychuang3, schung, yclu4, dri-devel, devicetree, linux-arm-kernel,
linux-kernel
In-Reply-To: <b08277e8a9350f6c68c9774b1b6185b1eb71cd1e.camel@iscas.ac.cn>
On 6/18/2026 6:24 PM, Icenowy Zheng wrote:
> 在 2026-06-17三的 18:25 +0800,Joey Lu写道:
>> On 6/15/2026 4:19 PM, Icenowy Zheng wrote:
>>> 在 2026-06-15一的 14:49 +0800,Joey Lu写道:
>>>> The existing schema hard-codes the five-clock/three-reset/dual-
>>>> port
>>>> topology of the DC8200 IP block, preventing reuse for single-
>>>> output
>>>> variants such as the Verisilicon DCUltraLite used in the Nuvoton
>>>> MA35D1
>>>> SoC.
>>>>
>>>> Rework the schema so that variant-specific constraints are
>>>> expressed
>>>> via
>>>> allOf/if blocks:
>>>>
>>>> - Add nuvoton,ma35d1-dcu to the SoC-specific compatible enum.
>>>> The
>>>> generic verisilicon,dc fallback remains the driver-binding
>>>> string.
>>>> - Move clock and reset items descriptions into the per-variant
>>>> allOf/if
>>>> blocks; keep only minItems/maxItems at the top level so the
>>>> base
>>>> schema
>>>> accepts all variants.
>>>> - Restore full items lists for clock-names and reset-names at the
>>>> top
>>>> level with minItems so the names are validated against the
>>>> descriptions.
>>>> - Keep ports in the global required list and keep
>>>> additionalProperties: false.
>>>> - Add an allOf/if block for thead,th1520-dc8200: five-clock
>>>> (core,
>>>> axi,
>>>> ahb, pix0, pix1), three-reset (core, axi, ahb), required
>>>> resets.
>>>> - Add an allOf/if block for nuvoton,ma35d1-dcu: two-clock (core,
>>>> pix0),
>>>> one-reset (core), required resets.
>>>>
>>>> Signed-off-by: Joey Lu <a0987203069@gmail.com>
>>>> ---
>>>> .../bindings/display/verisilicon,dc.yaml | 80
>>>> +++++++++++++++++--
>>>> 1 file changed, 73 insertions(+), 7 deletions(-)
>>>>
>>>> diff --git
>>>> a/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
>>>> b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
>>>> index 9dc35ab973f2..0c41286b8223 100644
>>>> ---
>>>> a/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
>>>> +++
>>>> b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
>>>> @@ -17,6 +17,7 @@ properties:
>>>> items:
>>>> - enum:
>>>> - thead,th1520-dc8200
>>>> + - nuvoton,ma35d1-dcu
>>>> - const: verisilicon,dc # DC IPs have discoverable
>>>> ID/revision
>>>> registers
>>>>
>>>> reg:
>>>> @@ -26,14 +27,12 @@ properties:
>>>> maxItems: 1
>>>>
>>>> clocks:
>>>> - items:
>>>> - - description: DC Core clock
>>>> - - description: DMA AXI bus clock
>>>> - - description: Configuration AHB bus clock
>>>> - - description: Pixel clock of output 0
>>>> - - description: Pixel clock of output 1
>>> Clock descriptions should still be in the global part instead of
>>> the
>>> per-compatible part.
>>>
>>> In the per-compatible part, clock-names should be constraint for
>>> SoCs.
>> I will move the `items:` clock descriptions back into the global
>> `clocks:` property, covering all five possible clocks. In the
>> per-compatible sections I will remove the description items and only
>> constrain `clocks: minItems/maxItems` and `clock-names:
>> minItems/maxItems`; for nuvoton,ma35d1-dcu I will additionally
>> override
>> `clock-names: items:` to the two names actually used (core, pix0).
> Yes, this should be the correct practice, although I wonder whether the
> minItems and maxItems properties are needed globally (because these two
> seem to have default implicit value).
>
> BTW the MA35D1 manual in fact shows 4 clocks for "DCUltra" in the clock
> tree, maybe the DT binding needs to be reconsidered?
>
> Thanks,
> Icenowy
I will drop the global `minItems`/`maxItems` on `clocks` and
`clock-names` in v5, as they are redundant with the implicit defaults.
Regarding the 4-clock question: the TRM clock tree diagram does show
four paths reaching DCUltra (display core mux/gate, AXI ACLK, AHB HCLK,
and the pixel clock divider). However, the MA35D1 hardware provides only
one software-controllable enable bit (SYSCLK0[26]) that gates the core
clock together with the AXI and AHB bus clocks through shared ICG cells;
there are no separate register bits for the bus clocks alone. Due to
this hardware design constraint, the `clk-ma35d1` driver is
intentionally designed to register only three DCU-related CCF nodes:
`dcu_mux` (ID 61, an internal routing mux), `dcu_gate` (ID 62, the
single gate at SYSCLK0 bit 26), and `dcup_div` (ID 63, the pixel divider
from VPLL at CLKDIV0[18:16]), with no independent AXI or AHB gate
entries for DCU. Since the DT binding can only reference clock handles
that the platform clock driver actually provides, the MA35D1 binding
will remain at two clock entries: "core" mapped to `DCU_GATE` and "pix0"
mapped to `DCUP_DIV`.
Thanks.
>
>>>> + minItems: 2
>>>> + maxItems: 5
>>>>
>>>> clock-names:
>>>> + minItems: 2
>>>> + maxItems: 5
>>>> items:
>>>> - const: core
>>>> - const: axi
^ permalink raw reply
* [PATCH v5 3/4] reset: cix: add sky1 audss auxiliary reset driver
From: joakim.zhang @ 2026-06-22 2:25 UTC (permalink / raw)
To: mturquette, sboyd, bmasney, robh, krzk+dt, conor+dt, p.zabel,
gary.yang
Cc: cix-kernel-upstream, linux-clk, devicetree, linux-kernel,
linux-arm-kernel, Joakim Zhang
In-Reply-To: <20260622022520.3127103-1-joakim.zhang@cixtech.com>
From: Joakim Zhang <joakim.zhang@cixtech.com>
Add an auxiliary reset controller driver for the AUDSS CRU. Sixteen
software reset lines for audio subsystem peripherals are controlled
through one register in the CRU register map.
The driver is created by the AUDSS clock platform driver and registers
the reset controller on the CRU device node.
Signed-off-by: Joakim Zhang <joakim.zhang@cixtech.com>
---
drivers/reset/Kconfig | 14 +++
drivers/reset/Makefile | 1 +
drivers/reset/reset-sky1-audss.c | 192 +++++++++++++++++++++++++++++++
3 files changed, 207 insertions(+)
create mode 100644 drivers/reset/reset-sky1-audss.c
diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
index d009eb0849a3..f74859b292ae 100644
--- a/drivers/reset/Kconfig
+++ b/drivers/reset/Kconfig
@@ -300,6 +300,20 @@ config RESET_SKY1
help
This enables the reset controller for Cix Sky1.
+config RESET_SKY1_AUDSS
+ tristate "Cix Sky1 Audio Subsystem reset controller"
+ depends on ARCH_CIX || COMPILE_TEST
+ select AUXILIARY_BUS
+ select REGMAP_MMIO
+ default CLK_SKY1_AUDSS
+ help
+ Support for block-level software reset lines in the Cix Sky1
+ Audio Subsystem (AUDSS) Clock and Reset Unit. Sixteen reset
+ outputs for audio peripherals are controlled through the CRU
+ register map. The driver binds as an auxiliary device from
+ the AUDSS clock driver. Say M or Y here if you want to build
+ this driver.
+
config RESET_SOCFPGA
bool "SoCFPGA Reset Driver" if COMPILE_TEST && (!ARM || !ARCH_INTEL_SOCFPGA)
default ARM && ARCH_INTEL_SOCFPGA
diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile
index 3e52569bd276..e81407ea3e29 100644
--- a/drivers/reset/Makefile
+++ b/drivers/reset/Makefile
@@ -39,6 +39,7 @@ obj-$(CONFIG_RESET_RZV2H_USB2PHY) += reset-rzv2h-usb2phy.o
obj-$(CONFIG_RESET_SCMI) += reset-scmi.o
obj-$(CONFIG_RESET_SIMPLE) += reset-simple.o
obj-$(CONFIG_RESET_SKY1) += reset-sky1.o
+obj-$(CONFIG_RESET_SKY1_AUDSS) += reset-sky1-audss.o
obj-$(CONFIG_RESET_SOCFPGA) += reset-socfpga.o
obj-$(CONFIG_RESET_SUNPLUS) += reset-sunplus.o
obj-$(CONFIG_RESET_SUNXI) += reset-sunxi.o
diff --git a/drivers/reset/reset-sky1-audss.c b/drivers/reset/reset-sky1-audss.c
new file mode 100644
index 000000000000..f29fe3554ec1
--- /dev/null
+++ b/drivers/reset/reset-sky1-audss.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Cix Sky1 Audio Subsystem reset controller driver
+ *
+ * Copyright 2026 Cix Technology Group Co., Ltd.
+ */
+
+#include <dt-bindings/reset/cix,sky1-audss-reset.h>
+
+#include <linux/auxiliary_bus.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/regmap.h>
+#include <linux/reset-controller.h>
+
+#define SKY1_RESET_SLEEP_MIN_US 50
+#define SKY1_RESET_SLEEP_MAX_US 100
+
+#define AUDSS_SW_RST 0x78
+
+struct sky1_audss_reset_map {
+ unsigned int offset;
+ unsigned int mask;
+};
+
+struct sky1_audss_reset {
+ struct reset_controller_dev rcdev;
+ struct regmap *regmap;
+ const struct sky1_audss_reset_map *map;
+};
+
+static const struct sky1_audss_reset_map sky1_audss_reset_map[] = {
+ [AUDSS_I2S0_SW_RST] = { AUDSS_SW_RST, BIT(0) },
+ [AUDSS_I2S1_SW_RST] = { AUDSS_SW_RST, BIT(1) },
+ [AUDSS_I2S2_SW_RST] = { AUDSS_SW_RST, BIT(2) },
+ [AUDSS_I2S3_SW_RST] = { AUDSS_SW_RST, BIT(3) },
+ [AUDSS_I2S4_SW_RST] = { AUDSS_SW_RST, BIT(4) },
+ [AUDSS_I2S5_SW_RST] = { AUDSS_SW_RST, BIT(5) },
+ [AUDSS_I2S6_SW_RST] = { AUDSS_SW_RST, BIT(6) },
+ [AUDSS_I2S7_SW_RST] = { AUDSS_SW_RST, BIT(7) },
+ [AUDSS_I2S8_SW_RST] = { AUDSS_SW_RST, BIT(8) },
+ [AUDSS_I2S9_SW_RST] = { AUDSS_SW_RST, BIT(9) },
+ [AUDSS_WDT_SW_RST] = { AUDSS_SW_RST, BIT(10) },
+ [AUDSS_TIMER_SW_RST] = { AUDSS_SW_RST, BIT(11) },
+ [AUDSS_MB0_SW_RST] = { AUDSS_SW_RST, BIT(12) },
+ [AUDSS_MB1_SW_RST] = { AUDSS_SW_RST, BIT(13) },
+ [AUDSS_HDA_SW_RST] = { AUDSS_SW_RST, BIT(14) },
+ [AUDSS_DMAC_SW_RST] = { AUDSS_SW_RST, BIT(15) },
+};
+
+static struct sky1_audss_reset *to_sky1_audss_reset(struct reset_controller_dev *rcdev)
+{
+ return container_of(rcdev, struct sky1_audss_reset, rcdev);
+}
+
+static int sky1_audss_reset_set(struct reset_controller_dev *rcdev,
+ unsigned long id, bool assert)
+{
+ struct sky1_audss_reset *priv = to_sky1_audss_reset(rcdev);
+ const struct sky1_audss_reset_map *signal = &priv->map[id];
+ unsigned int value = assert ? 0 : signal->mask;
+
+ return regmap_update_bits(priv->regmap, signal->offset, signal->mask, value);
+}
+
+static int sky1_audss_reset_assert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ sky1_audss_reset_set(rcdev, id, true);
+ usleep_range(SKY1_RESET_SLEEP_MIN_US, SKY1_RESET_SLEEP_MAX_US);
+ return 0;
+}
+
+static int sky1_audss_reset_deassert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ sky1_audss_reset_set(rcdev, id, false);
+ usleep_range(SKY1_RESET_SLEEP_MIN_US, SKY1_RESET_SLEEP_MAX_US);
+ return 0;
+}
+
+static int sky1_audss_reset(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ sky1_audss_reset_assert(rcdev, id);
+ sky1_audss_reset_deassert(rcdev, id);
+ return 0;
+}
+
+static int sky1_audss_reset_status(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct sky1_audss_reset *priv = to_sky1_audss_reset(rcdev);
+ const struct sky1_audss_reset_map *signal = &priv->map[id];
+ unsigned int value;
+
+ regmap_read(priv->regmap, signal->offset, &value);
+ return !!(value & signal->mask);
+}
+
+static const struct reset_control_ops sky1_audss_reset_ops = {
+ .reset = sky1_audss_reset,
+ .assert = sky1_audss_reset_assert,
+ .deassert = sky1_audss_reset_deassert,
+ .status = sky1_audss_reset_status,
+};
+
+static const struct regmap_config sky1_audss_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static void sky1_audss_reset_iounmap(void *data)
+{
+ iounmap(data);
+}
+
+static int sky1_audss_reset_get_regmap(struct sky1_audss_reset *priv)
+{
+ struct device *dev = priv->rcdev.dev;
+ void __iomem *base;
+ int ret;
+
+ priv->regmap = dev_get_regmap(dev->parent, NULL);
+ if (priv->regmap)
+ return 0;
+
+ base = of_iomap(dev->parent->of_node, 0);
+ if (!base)
+ return dev_err_probe(dev, -ENOMEM, "failed to iomap address space\n");
+
+ ret = devm_add_action_or_reset(dev, sky1_audss_reset_iounmap, base);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register iounmap action\n");
+
+ priv->regmap = devm_regmap_init_mmio(dev, base, &sky1_audss_regmap_config);
+ if (IS_ERR(priv->regmap))
+ return dev_err_probe(dev, PTR_ERR(priv->regmap),
+ "failed to initialize regmap\n");
+
+ return 0;
+}
+
+static int sky1_audss_reset_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct sky1_audss_reset *priv;
+ struct device *dev = &adev->dev;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->map = sky1_audss_reset_map;
+ priv->rcdev.owner = THIS_MODULE;
+ priv->rcdev.nr_resets = ARRAY_SIZE(sky1_audss_reset_map);
+ priv->rcdev.ops = &sky1_audss_reset_ops;
+ priv->rcdev.of_node = dev->parent->of_node;
+ priv->rcdev.dev = dev;
+ priv->rcdev.of_reset_n_cells = 1;
+
+ dev_set_drvdata(dev, priv);
+
+ ret = sky1_audss_reset_get_regmap(priv);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to get regmap\n");
+
+ return devm_reset_controller_register(dev, &priv->rcdev);
+}
+
+static const struct auxiliary_device_id sky1_audss_reset_ids[] = {
+ { .name = "clk_sky1_audss.reset" },
+ { }
+};
+MODULE_DEVICE_TABLE(auxiliary, sky1_audss_reset_ids);
+
+static struct auxiliary_driver sky1_audss_reset_driver = {
+ .probe = sky1_audss_reset_probe,
+ .id_table = sky1_audss_reset_ids,
+};
+
+module_auxiliary_driver(sky1_audss_reset_driver);
+
+MODULE_AUTHOR("Joakim Zhang <joakim.zhang@cixtech.com>");
+MODULE_DESCRIPTION("Cix Sky1 Audio Subsystem reset driver");
+MODULE_LICENSE("GPL");
--
2.50.1
^ permalink raw reply related
* [PATCH v5 1/4] dt-bindings: soc: cix: add sky1 audss cru controller
From: joakim.zhang @ 2026-06-22 2:25 UTC (permalink / raw)
To: mturquette, sboyd, bmasney, robh, krzk+dt, conor+dt, p.zabel,
gary.yang
Cc: cix-kernel-upstream, linux-clk, devicetree, linux-kernel,
linux-arm-kernel, Joakim Zhang
In-Reply-To: <20260622022520.3127103-1-joakim.zhang@cixtech.com>
From: Joakim Zhang <joakim.zhang@cixtech.com>
The Cix Sky1 Audio Subsystem (AUDSS) Clock and Reset Unit (CRU)
groups clock muxing, gating and block-level software reset control
in a single register block.
Signed-off-by: Joakim Zhang <joakim.zhang@cixtech.com>
---
.../bindings/soc/cix/cix,sky1-audss-cru.yaml | 92 +++++++++++++++++++
.../dt-bindings/clock/cix,sky1-audss-clock.h | 60 ++++++++++++
.../dt-bindings/reset/cix,sky1-audss-reset.h | 25 +++++
3 files changed, 177 insertions(+)
create mode 100644 Documentation/devicetree/bindings/soc/cix/cix,sky1-audss-cru.yaml
create mode 100644 include/dt-bindings/clock/cix,sky1-audss-clock.h
create mode 100644 include/dt-bindings/reset/cix,sky1-audss-reset.h
diff --git a/Documentation/devicetree/bindings/soc/cix/cix,sky1-audss-cru.yaml b/Documentation/devicetree/bindings/soc/cix/cix,sky1-audss-cru.yaml
new file mode 100644
index 000000000000..d5a6d771bc6f
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/cix/cix,sky1-audss-cru.yaml
@@ -0,0 +1,92 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/soc/cix/cix,sky1-audss-cru.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Cix Sky1 audio subsystem clock and reset unit
+
+maintainers:
+ - Joakim Zhang <joakim.zhang@cixtech.com>
+
+description: |
+ The Cix Sky1 Audio Subsystem (AUDSS) Clock and Reset Unit (CRU) groups
+ audio-related clock muxing, gating and block-level software reset control
+ in a single register block.
+
+ A single device node exposes both the clock controller and software reset
+ lines. The clock driver registers as a platform driver; the reset controller
+ is registered by an auxiliary driver bound from the clock driver.
+
+ Four SoC-level reference clocks listed in clocks/clock-names feed the AUDSS
+ clock tree. Internal AUDSS clocks are exposed via #clock-cells; indices are
+ defined in include/dt-bindings/clock/cix,sky1-audss-clock.h.
+
+ Block-level software reset indices are exposed via #reset-cells; indices
+ are defined in include/dt-bindings/reset/cix,sky1-audss-reset.h.
+
+ The SoC syscon NoC (or bus) reset is described via resets. The audio
+ subsystem power domain is described via power-domains.
+
+properties:
+ compatible:
+ const: cix,sky1-audss-cru
+
+ reg:
+ maxItems: 1
+
+ '#clock-cells':
+ const: 1
+ description:
+ Clock indices are defined in include/dt-bindings/clock/cix,sky1-audss-clock.h.
+
+ '#reset-cells':
+ const: 1
+ description:
+ Reset indices are defined in include/dt-bindings/reset/cix,sky1-audss-reset.h.
+
+ clocks:
+ items:
+ - description: I2S parent clock for sampling rates multiple of 8kHz.
+ - description: I2S parent clock for sampling rates multiple of 11.025kHz.
+ - description: Clock feeding most devices in AUDSS (NOC, DSP, SRAM, HDA, DMAC, I2S, and mailbox).
+ - description: Clock feeding HDA, timer and watchdog, which is a dedicated 48 MHz clock.
+
+ clock-names:
+ items:
+ - const: x8k
+ - const: x11k
+ - const: sys
+ - const: 48m
+
+ power-domains:
+ maxItems: 1
+
+ resets:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - '#clock-cells'
+ - '#reset-cells'
+ - clocks
+ - clock-names
+ - power-domains
+ - resets
+
+additionalProperties: false
+
+examples:
+ - |
+ audss_cru: clock-controller@7110000 {
+ compatible = "cix,sky1-audss-cru";
+ reg = <0x7110000 0x10000>;
+ #clock-cells = <1>;
+ #reset-cells = <1>;
+ clocks = <&scmi_clk 76>, <&scmi_clk 78>,
+ <&scmi_clk 70>, <&scmi_clk 71>;
+ clock-names = "x8k", "x11k", "sys", "48m";
+ power-domains = <&smc_devpd 0>;
+ resets = <&s5_syscon 31>;
+ };
diff --git a/include/dt-bindings/clock/cix,sky1-audss-clock.h b/include/dt-bindings/clock/cix,sky1-audss-clock.h
new file mode 100644
index 000000000000..7e9bd3e6c7a1
--- /dev/null
+++ b/include/dt-bindings/clock/cix,sky1-audss-clock.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Copyright 2026 Cix Technology Group Co., Ltd.
+ */
+
+#ifndef _DT_BINDINGS_CLK_CIX_SKY1_AUDSS_CLOCK_H
+#define _DT_BINDINGS_CLK_CIX_SKY1_AUDSS_CLOCK_H
+
+#define CLK_AUD_CLK4_DIV2 0
+#define CLK_AUD_CLK4_DIV4 1
+#define CLK_AUD_CLK5_DIV2 2
+
+#define CLK_DSP_CLK 3
+#define CLK_DSP_BCLK 4
+#define CLK_DSP_PBCLK 5
+
+#define CLK_SRAM_AXI 6
+
+#define CLK_HDA_SYS 7
+#define CLK_HDA_HDA 8
+
+#define CLK_DMAC_AXI 9
+
+#define CLK_WDG_APB 10
+#define CLK_WDG_WDG 11
+
+#define CLK_TIMER_APB 12
+#define CLK_TIMER_TIMER 13
+
+#define CLK_MB_0_APB 14 /* MB0: ap->dsp */
+#define CLK_MB_1_APB 15 /* MB1: dsp->ap */
+
+#define CLK_I2S0_APB 16
+#define CLK_I2S1_APB 17
+#define CLK_I2S2_APB 18
+#define CLK_I2S3_APB 19
+#define CLK_I2S4_APB 20
+#define CLK_I2S5_APB 21
+#define CLK_I2S6_APB 22
+#define CLK_I2S7_APB 23
+#define CLK_I2S8_APB 24
+#define CLK_I2S9_APB 25
+#define CLK_I2S0 26
+#define CLK_I2S1 27
+#define CLK_I2S2 28
+#define CLK_I2S3 29
+#define CLK_I2S4 30
+#define CLK_I2S5 31
+#define CLK_I2S6 32
+#define CLK_I2S7 33
+#define CLK_I2S8 34
+#define CLK_I2S9 35
+
+#define CLK_MCLK0 36
+#define CLK_MCLK1 37
+#define CLK_MCLK2 38
+#define CLK_MCLK3 39
+#define CLK_MCLK4 40
+
+#endif
diff --git a/include/dt-bindings/reset/cix,sky1-audss-reset.h b/include/dt-bindings/reset/cix,sky1-audss-reset.h
new file mode 100644
index 000000000000..fe4116866b63
--- /dev/null
+++ b/include/dt-bindings/reset/cix,sky1-audss-reset.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause */
+/*
+ * Copyright 2026 Cix Technology Group Co., Ltd.
+ */
+#ifndef DT_BINDINGS_RESET_CIX_SKY1_AUDSS_RESET_H
+#define DT_BINDINGS_RESET_CIX_SKY1_AUDSS_RESET_H
+
+#define AUDSS_I2S0_SW_RST 0
+#define AUDSS_I2S1_SW_RST 1
+#define AUDSS_I2S2_SW_RST 2
+#define AUDSS_I2S3_SW_RST 3
+#define AUDSS_I2S4_SW_RST 4
+#define AUDSS_I2S5_SW_RST 5
+#define AUDSS_I2S6_SW_RST 6
+#define AUDSS_I2S7_SW_RST 7
+#define AUDSS_I2S8_SW_RST 8
+#define AUDSS_I2S9_SW_RST 9
+#define AUDSS_WDT_SW_RST 10
+#define AUDSS_TIMER_SW_RST 11
+#define AUDSS_MB0_SW_RST 12
+#define AUDSS_MB1_SW_RST 13
+#define AUDSS_HDA_SW_RST 14
+#define AUDSS_DMAC_SW_RST 15
+
+#endif
--
2.50.1
^ permalink raw reply related
* [PATCH v5 2/4] clk: cix: add sky1 audss clock controller
From: joakim.zhang @ 2026-06-22 2:25 UTC (permalink / raw)
To: mturquette, sboyd, bmasney, robh, krzk+dt, conor+dt, p.zabel,
gary.yang
Cc: cix-kernel-upstream, linux-clk, devicetree, linux-kernel,
linux-arm-kernel, Joakim Zhang
In-Reply-To: <20260622022520.3127103-1-joakim.zhang@cixtech.com>
From: Joakim Zhang <joakim.zhang@cixtech.com>
Add a platform driver for the Cix Sky1 AUDSS CRU. The driver maps
the CRU registers and registers mux, divider and gate clocks for
DSP, SRAM, HDA, DMAC, I2S, mailbox, watchdog and timer blocks.
Four SoC-level audio reference clocks are enabled as inputs to the
internal clock tree. The driver releases the AUDSS NOC reset, enables
runtime PM and instantiates the auxiliary reset device.
Signed-off-by: Joakim Zhang <joakim.zhang@cixtech.com>
---
drivers/clk/Kconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/cix/Kconfig | 16 +
drivers/clk/cix/Makefile | 3 +
drivers/clk/cix/clk-sky1-audss.c | 1201 ++++++++++++++++++++++++++++++
5 files changed, 1222 insertions(+)
create mode 100644 drivers/clk/cix/Kconfig
create mode 100644 drivers/clk/cix/Makefile
create mode 100644 drivers/clk/cix/clk-sky1-audss.c
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 1717ce75a907..cfcaab39068a 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -509,6 +509,7 @@ source "drivers/clk/actions/Kconfig"
source "drivers/clk/analogbits/Kconfig"
source "drivers/clk/aspeed/Kconfig"
source "drivers/clk/bcm/Kconfig"
+source "drivers/clk/cix/Kconfig"
source "drivers/clk/eswin/Kconfig"
source "drivers/clk/hisilicon/Kconfig"
source "drivers/clk/imgtec/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index cc108a75a900..87c992f0df54 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -119,6 +119,7 @@ obj-$(CONFIG_ARCH_ARTPEC) += axis/
obj-$(CONFIG_ARC_PLAT_AXS10X) += axs10x/
obj-y += bcm/
obj-$(CONFIG_ARCH_BERLIN) += berlin/
+obj-y += cix/
obj-$(CONFIG_ARCH_DAVINCI) += davinci/
obj-$(CONFIG_COMMON_CLK_ESWIN) += eswin/
obj-$(CONFIG_ARCH_HISI) += hisilicon/
diff --git a/drivers/clk/cix/Kconfig b/drivers/clk/cix/Kconfig
new file mode 100644
index 000000000000..c92a9a873893
--- /dev/null
+++ b/drivers/clk/cix/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0
+# Audio subsystem clock support for Cixtech SoC family
+menu "Clock support for Cixtech audss"
+
+config CLK_SKY1_AUDSS
+ tristate "Cixtech Sky1 Audio Subsystem Clock Driver"
+ depends on ARCH_CIX || COMPILE_TEST
+ select AUXILIARY_BUS
+ select REGMAP_MMIO
+ select RESET_CONTROLLER
+ help
+ Support for the Audio Subsystem clock controller present on
+ Cixtech Sky1 SoC. This driver provides mux, divider and gate
+ clocks for DSP, I2S, HDA and related blocks in the audio
+ subsystem. Say M or Y here if you want to build this driver.
+endmenu
diff --git a/drivers/clk/cix/Makefile b/drivers/clk/cix/Makefile
new file mode 100644
index 000000000000..bc612f1d08b2
--- /dev/null
+++ b/drivers/clk/cix/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_CLK_SKY1_AUDSS) += clk-sky1-audss.o
diff --git a/drivers/clk/cix/clk-sky1-audss.c b/drivers/clk/cix/clk-sky1-audss.c
new file mode 100644
index 000000000000..8a4ab2d9419c
--- /dev/null
+++ b/drivers/clk/cix/clk-sky1-audss.c
@@ -0,0 +1,1201 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright 2026 Cix Technology Group Co., Ltd.
+
+#include <linux/auxiliary_bus.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#include <dt-bindings/clock/cix,sky1-audss-clock.h>
+
+#define INFO_HIFI0 0x00
+#define INFO_CLK_GATE 0x10
+#define INFO_CLK_DIV 0x14
+#define INFO_CLK_MUX 0x18
+#define INFO_MCLK 0x70
+
+#define SKY1_AUDSS_CLK_PARENTS_CNT 4
+#define SKY1_AUDSS_NUM_CLKS (CLK_MCLK4 + 1)
+
+static u32 sky1_reg_save[][2] = {
+ { INFO_HIFI0, 0 },
+ { INFO_CLK_GATE, 0 },
+ { INFO_CLK_DIV, 0 },
+ { INFO_CLK_MUX, 0 },
+ { INFO_MCLK, 0 },
+};
+
+static const char * const sky1_audss_clk_names[SKY1_AUDSS_CLK_PARENTS_CNT] = {
+ "x8k", "x11k", "sys", "48m",
+};
+
+static const u32 sky1_clk_rate_default[SKY1_AUDSS_CLK_PARENTS_CNT] = {
+ 294912000,
+ 270950400,
+ 800000000,
+ 48000000,
+};
+
+static const char * const dsp_clk_parent[] = {
+ "audio_clk4"
+};
+
+static const char * const dsp_bclk_parent[] = {
+ "audio_clk4_div2"
+};
+
+static const char * const dsp_pbclk_parent[] = {
+ "audio_clk4_div4"
+};
+
+static const char * const sram_axi_parent[] = {
+ "audio_clk4_div2"
+};
+
+static const char * const hda_sys_parent[] = {
+ "audio_clk4_div2"
+};
+
+static const char * const hda_hda_parent[] = {
+ "audio_clk5"
+};
+
+static const char * const dmac_axi_parent[] = {
+ "audio_clk4_div2"
+};
+
+static const char * const wdg_apb_parent[] = {
+ "audio_clk5_div2"
+};
+
+static const char * const wdg_wdg_parent[] = {
+ "audio_clk5_div2"
+};
+
+static const char * const timer_apb_parent[] = {
+ "audio_clk4_div4"
+};
+
+static const char * const timer_timer_parent[] = {
+ "audio_clk5_div2"
+};
+
+static const char * const mailbox_apb_parent[] = {
+ "audio_clk4_div4"
+};
+
+static const char * const i2s_apb_parent[] = {
+ "audio_clk4_div4"
+};
+
+static const char * const i2s0_parents[] = {
+ "audio_clk0", "audio_clk2"
+};
+
+static const char * const i2s1_parents[] = {
+ "audio_clk0", "audio_clk2"
+};
+
+static const char * const i2s2_parents[] = {
+ "audio_clk0", "audio_clk2"
+};
+
+static const char * const i2s3_parents[] = {
+ "audio_clk0", "audio_clk2"
+};
+
+static const char * const i2s4_parents[] = {
+ "audio_clk0", "audio_clk2"
+};
+
+static const char * const i2s5_parents[] = {
+ "audio_clk0", "audio_clk2"
+};
+
+static const char * const i2s6_parents[] = {
+ "audio_clk0", "audio_clk2"
+};
+
+static const char * const i2s7_parents[] = {
+ "audio_clk0", "audio_clk2"
+};
+
+static const char * const i2s8_parents[] = {
+ "audio_clk0", "audio_clk2"
+};
+
+static const char * const i2s9_parents[] = {
+ "audio_clk0", "audio_clk2"
+};
+
+static const char * const mclk_parents[] = {
+ "audio_clk0", "audio_clk2"
+};
+
+static const u32 i2s3_mux_table[] = { 0, 2 };
+static const u32 i2s4_mux_table[] = { 0, 2 };
+
+/*
+ * audss composite clock definition
+ */
+struct muxdiv_cfg {
+ int offset;
+ u8 shift;
+ u8 width;
+ u8 flags;
+};
+
+struct gate_cfg {
+ int offset;
+ u8 shift;
+ u8 flags;
+};
+
+struct composite_clk_cfg {
+ u32 id;
+ const char * const name;
+ const char * const *parent_names;
+ int num_parents;
+ const u32 *mux_table;
+ struct muxdiv_cfg *mux_cfg;
+ struct muxdiv_cfg *div_cfg;
+ struct gate_cfg *gate_cfg;
+ unsigned long flags;
+};
+
+#define CFG(_id,\
+ _name,\
+ _parent_names,\
+ _mux_table,\
+ _mux_offset, _mux_shift, _mux_width, _mux_flags,\
+ _div_offset, _div_shift, _div_width, _div_flags,\
+ _gate_offset, _gate_shift, _gate_flags,\
+ _flags)\
+{\
+ .id = _id,\
+ .name = _name,\
+ .parent_names = _parent_names,\
+ .num_parents = ARRAY_SIZE(_parent_names),\
+ .mux_table = _mux_table,\
+ .mux_cfg = &(struct muxdiv_cfg) { _mux_offset, _mux_shift, _mux_width, _mux_flags },\
+ .div_cfg = &(struct muxdiv_cfg) { _div_offset, _div_shift, _div_width, _div_flags },\
+ .gate_cfg = &(struct gate_cfg) { _gate_offset, _gate_shift, _gate_flags },\
+ .flags = _flags,\
+}
+
+static const struct composite_clk_cfg sky1_audss_clks[] = {
+ /* dsp */
+ CFG(CLK_DSP_CLK,
+ "audss_dsp_clk",
+ dsp_clk_parent,
+ NULL,
+ -1, 0, 0, 0,
+ INFO_CLK_DIV, 0, 2, 0,
+ INFO_HIFI0, 0, 0,
+ 0),
+ CFG(CLK_DSP_BCLK,
+ "audss_dsp_bclk",
+ dsp_bclk_parent,
+ NULL,
+ -1, 0, 0, 0,
+ INFO_CLK_DIV, 0, 2, 0,
+ -1, 0, 0,
+ 0),
+ CFG(CLK_DSP_PBCLK,
+ "audss_dsp_pbclk",
+ dsp_pbclk_parent,
+ NULL,
+ -1, 0, 0, 0,
+ INFO_CLK_DIV, 0, 2, 0,
+ -1, 0, 0,
+ 0),
+ /* sram */
+ CFG(CLK_SRAM_AXI,
+ "audss_sram_axi",
+ sram_axi_parent,
+ NULL,
+ -1, 0, 0, 0,
+ INFO_CLK_DIV, 0, 2, 0,
+ INFO_CLK_GATE, 16, 0,
+ 0),
+ /* hda */
+ CFG(CLK_HDA_SYS,
+ "audss_hda_sys",
+ hda_sys_parent,
+ NULL,
+ -1, 0, 0, 0,
+ INFO_CLK_DIV, 0, 2, 0,
+ INFO_CLK_GATE, 14, 0,
+ 0),
+ CFG(CLK_HDA_HDA,
+ "audss_hda_hda",
+ hda_hda_parent,
+ NULL,
+ -1, 0, 0, 0,
+ -1, 0, 0, 0,
+ INFO_CLK_GATE, 14, 0,
+ 0),
+ /* dmac */
+ CFG(CLK_DMAC_AXI,
+ "audss_dmac_axi",
+ dmac_axi_parent,
+ NULL,
+ -1, 0, 0, 0,
+ INFO_CLK_DIV, 0, 2, 0,
+ INFO_CLK_GATE, 15, 0,
+ 0),
+ /* wdg */
+ CFG(CLK_WDG_APB,
+ "audss_wdg_apb",
+ wdg_apb_parent,
+ NULL,
+ -1, 0, 0, 0,
+ -1, 0, 0, 0,
+ INFO_CLK_GATE, 10, 0,
+ 0),
+ CFG(CLK_WDG_WDG,
+ "audss_wdg_wdg",
+ wdg_wdg_parent,
+ NULL,
+ -1, 0, 0, 0,
+ -1, 0, 0, 0,
+ INFO_CLK_GATE, 10, 0,
+ 0),
+ /* timer */
+ CFG(CLK_TIMER_APB,
+ "audss_timer_apb",
+ timer_apb_parent,
+ NULL,
+ -1, 0, 0, 0,
+ INFO_CLK_DIV, 0, 2, 0,
+ INFO_CLK_GATE, 11, 0,
+ 0),
+ CFG(CLK_TIMER_TIMER,
+ "audss_timer_timer",
+ timer_timer_parent,
+ NULL,
+ -1, 0, 0, 0,
+ -1, 0, 0, 0,
+ INFO_CLK_GATE, 11, 0,
+ 0),
+ /* mailbox: mb0(ap->dsp), mb1(dsp->ap) */
+ CFG(CLK_MB_0_APB,
+ "audss_mb_0_apb",
+ mailbox_apb_parent,
+ NULL,
+ -1, 0, 0, 0,
+ -1, 0, 0, 0,
+ INFO_CLK_GATE, 12, 0,
+ 0),
+ CFG(CLK_MB_1_APB,
+ "audss_mb_1_apb",
+ mailbox_apb_parent,
+ NULL,
+ -1, 0, 0, 0,
+ -1, 0, 0, 0,
+ INFO_CLK_GATE, 13, 0,
+ 0),
+ /* i2s */
+ CFG(CLK_I2S0_APB,
+ "audss_i2s0_apb",
+ i2s_apb_parent,
+ NULL,
+ -1, 0, 0, 0,
+ INFO_CLK_DIV, 0, 2, 0,
+ INFO_CLK_GATE, 0, 0,
+ 0),
+ CFG(CLK_I2S1_APB,
+ "audss_i2s1_apb",
+ i2s_apb_parent,
+ NULL,
+ -1, 0, 0, 0,
+ INFO_CLK_DIV, 0, 2, 0,
+ INFO_CLK_GATE, 1, 0,
+ 0),
+ CFG(CLK_I2S2_APB,
+ "audss_i2s2_apb",
+ i2s_apb_parent,
+ NULL,
+ -1, 0, 0, 0,
+ INFO_CLK_DIV, 0, 2, 0,
+ INFO_CLK_GATE, 2, 0,
+ 0),
+ CFG(CLK_I2S3_APB,
+ "audss_i2s3_apb",
+ i2s_apb_parent,
+ NULL,
+ -1, 0, 0, 0,
+ INFO_CLK_DIV, 0, 2, 0,
+ INFO_CLK_GATE, 3, 0,
+ 0),
+ CFG(CLK_I2S4_APB,
+ "audss_i2s4_apb",
+ i2s_apb_parent,
+ NULL,
+ -1, 0, 0, 0,
+ INFO_CLK_DIV, 0, 2, 0,
+ INFO_CLK_GATE, 4, 0,
+ 0),
+ CFG(CLK_I2S5_APB,
+ "audss_i2s5_apb",
+ i2s_apb_parent,
+ NULL,
+ -1, 0, 0, 0,
+ INFO_CLK_DIV, 0, 2, 0,
+ INFO_CLK_GATE, 5, 0,
+ 0),
+ CFG(CLK_I2S6_APB,
+ "audss_i2s6_apb",
+ i2s_apb_parent,
+ NULL,
+ -1, 0, 0, 0,
+ INFO_CLK_DIV, 0, 2, 0,
+ INFO_CLK_GATE, 6, 0,
+ 0),
+ CFG(CLK_I2S7_APB,
+ "audss_i2s7_apb",
+ i2s_apb_parent,
+ NULL,
+ -1, 0, 0, 0,
+ INFO_CLK_DIV, 0, 2, 0,
+ INFO_CLK_GATE, 7, 0,
+ 0),
+ CFG(CLK_I2S8_APB,
+ "audss_i2s8_apb",
+ i2s_apb_parent,
+ NULL,
+ -1, 0, 0, 0,
+ INFO_CLK_DIV, 0, 2, 0,
+ INFO_CLK_GATE, 8, 0,
+ 0),
+ CFG(CLK_I2S9_APB,
+ "audss_i2s9_apb",
+ i2s_apb_parent,
+ NULL,
+ -1, 0, 0, 0,
+ INFO_CLK_DIV, 0, 2, 0,
+ INFO_CLK_GATE, 9, 0,
+ 0),
+ CFG(CLK_I2S0,
+ "audss_i2s0",
+ i2s0_parents,
+ NULL,
+ INFO_CLK_MUX, 0, 2, 0,
+ INFO_CLK_DIV, 2, 2, 0,
+ INFO_CLK_GATE, 0, 0,
+ 0),
+ CFG(CLK_I2S1,
+ "audss_i2s1",
+ i2s1_parents,
+ NULL,
+ INFO_CLK_MUX, 2, 2, 0,
+ INFO_CLK_DIV, 4, 2, 0,
+ INFO_CLK_GATE, 1, 0,
+ 0),
+ CFG(CLK_I2S2,
+ "audss_i2s2",
+ i2s2_parents,
+ NULL,
+ INFO_CLK_MUX, 4, 2, 0,
+ INFO_CLK_DIV, 6, 2, 0,
+ INFO_CLK_GATE, 2, 0,
+ 0),
+ CFG(CLK_I2S3,
+ "audss_i2s3",
+ i2s3_parents,
+ i2s3_mux_table,
+ INFO_CLK_MUX, 6, 2, 0,
+ INFO_CLK_DIV, 8, 2, 0,
+ INFO_CLK_GATE, 3, 0,
+ 0),
+ CFG(CLK_I2S4,
+ "audss_i2s4",
+ i2s4_parents,
+ i2s4_mux_table,
+ INFO_CLK_MUX, 8, 2, 0,
+ INFO_CLK_DIV, 10, 2, 0,
+ INFO_CLK_GATE, 4, 0,
+ 0),
+ CFG(CLK_I2S5,
+ "audss_i2s5",
+ i2s5_parents,
+ NULL,
+ INFO_CLK_MUX, 10, 2, 0,
+ INFO_CLK_DIV, 12, 2, 0,
+ INFO_CLK_GATE, 5, 0,
+ 0),
+ CFG(CLK_I2S6,
+ "audss_i2s6",
+ i2s6_parents,
+ NULL,
+ INFO_CLK_MUX, 12, 2, 0,
+ INFO_CLK_DIV, 14, 2, 0,
+ INFO_CLK_GATE, 6, 0,
+ 0),
+ CFG(CLK_I2S7,
+ "audss_i2s7",
+ i2s7_parents,
+ NULL,
+ INFO_CLK_MUX, 14, 2, 0,
+ INFO_CLK_DIV, 16, 2, 0,
+ INFO_CLK_GATE, 7, 0,
+ 0),
+ CFG(CLK_I2S8,
+ "audss_i2s8",
+ i2s8_parents,
+ NULL,
+ INFO_CLK_MUX, 16, 2, 0,
+ INFO_CLK_DIV, 18, 2, 0,
+ INFO_CLK_GATE, 8, 0,
+ 0),
+ CFG(CLK_I2S9,
+ "audss_i2s9",
+ i2s9_parents,
+ NULL,
+ INFO_CLK_MUX, 18, 2, 0,
+ INFO_CLK_DIV, 20, 2, 0,
+ INFO_CLK_GATE, 9, 0,
+ 0),
+ /* mclk */
+ CFG(CLK_MCLK0,
+ "audss_mclk0",
+ mclk_parents,
+ NULL,
+ INFO_MCLK, 5, 1, 0,
+ -1, 0, 0, 0,
+ INFO_MCLK, 0, 0,
+ 0),
+ CFG(CLK_MCLK1,
+ "audss_mclk1",
+ mclk_parents,
+ NULL,
+ INFO_MCLK, 6, 1, 0,
+ -1, 0, 0, 0,
+ INFO_MCLK, 1, 0,
+ 0),
+ CFG(CLK_MCLK2,
+ "audss_mclk2",
+ mclk_parents,
+ NULL,
+ INFO_MCLK, 7, 1, 0,
+ -1, 0, 0, 0,
+ INFO_MCLK, 2, 0,
+ 0),
+ CFG(CLK_MCLK3,
+ "audss_mclk3",
+ mclk_parents,
+ NULL,
+ INFO_MCLK, 8, 1, 0,
+ -1, 0, 0, 0,
+ INFO_MCLK, 3, 0,
+ 0),
+ CFG(CLK_MCLK4,
+ "audss_mclk4",
+ mclk_parents,
+ NULL,
+ INFO_MCLK, 9, 1, 0,
+ -1, 0, 0, 0,
+ INFO_MCLK, 4, 0,
+ 0),
+};
+
+struct sky1_audss_clks_devtype_data {
+ u32 (*reg_save)[2];
+ size_t reg_save_size;
+ const char * const *clk_names;
+ size_t clk_num;
+ const u32 *clk_rate_default;
+ const struct composite_clk_cfg *clk_cfg;
+ size_t clk_cfg_size;
+};
+
+static const struct regmap_config sky1_audss_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+struct sky1_audss_clks_priv {
+ struct device *dev;
+ struct regmap *regmap_cru;
+ struct reset_control *rst_noc;
+ struct clk *clks[SKY1_AUDSS_CLK_PARENTS_CNT];
+ const struct sky1_audss_clks_devtype_data *devtype_data;
+ spinlock_t lock;
+ struct clk_hw_onecell_data *clk_data;
+};
+
+#if IS_ENABLED(CONFIG_RESET_SKY1_AUDSS)
+
+static int sky1_audss_reset_controller_register(struct device *dev)
+{
+ struct auxiliary_device *adev;
+
+ if (!of_property_present(dev->of_node, "#reset-cells"))
+ return 0;
+
+ adev = devm_auxiliary_device_create(dev, "reset", NULL);
+ if (!adev)
+ return -ENODEV;
+
+ return 0;
+}
+
+#else
+
+static int sky1_audss_reset_controller_register(struct device *dev)
+{
+ return 0;
+}
+
+#endif
+
+/*
+ * clk_ops for audss clock mux/divider/gate
+ */
+struct sky1_clk_divider {
+ struct clk_divider div;
+ struct regmap *regmap;
+ int offset;
+};
+
+struct sky1_clk_gate {
+ struct clk_gate gate;
+ struct regmap *regmap;
+ int offset;
+};
+
+struct sky1_clk_mux {
+ struct clk_mux mux;
+ struct regmap *regmap;
+ int offset;
+};
+
+static inline struct sky1_clk_mux *to_sky1_clk_mux(struct clk_mux *mux)
+{
+ return container_of(mux, struct sky1_clk_mux, mux);
+}
+
+static u8 sky1_audss_clk_mux_get_parent(struct clk_hw *hw)
+{
+ struct clk_mux *mux = to_clk_mux(hw);
+ struct sky1_clk_mux *sky1_mux = to_sky1_clk_mux(mux);
+ u32 val;
+
+ regmap_read(sky1_mux->regmap, sky1_mux->offset, &val);
+ val = val >> mux->shift;
+ val &= mux->mask;
+
+ return clk_mux_val_to_index(hw, mux->table, mux->flags, val);
+}
+
+static int sky1_audss_clk_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct clk_mux *mux = to_clk_mux(hw);
+ u32 val = clk_mux_index_to_val(mux->table, mux->flags, index);
+ struct sky1_clk_mux *sky1_mux = to_sky1_clk_mux(mux);
+ unsigned long flags = 0;
+ u32 reg;
+
+ if (mux->lock)
+ spin_lock_irqsave(mux->lock, flags);
+ else
+ __acquire(mux->lock);
+
+ if (mux->flags & CLK_MUX_HIWORD_MASK) {
+ reg = mux->mask << (mux->shift + 16);
+ } else {
+ regmap_read(sky1_mux->regmap, sky1_mux->offset, ®);
+ reg &= ~(mux->mask << mux->shift);
+ }
+ val = val << mux->shift;
+ reg |= val;
+ regmap_write(sky1_mux->regmap, sky1_mux->offset, reg);
+
+ if (mux->lock)
+ spin_unlock_irqrestore(mux->lock, flags);
+ else
+ __release(mux->lock);
+
+ return 0;
+}
+
+static int sky1_audss_clk_mux_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct clk_mux *mux = to_clk_mux(hw);
+
+ return clk_mux_determine_rate_flags(hw, req, mux->flags);
+}
+
+static const struct clk_ops sky1_audss_clk_mux_ops = {
+ .get_parent = sky1_audss_clk_mux_get_parent,
+ .set_parent = sky1_audss_clk_mux_set_parent,
+ .determine_rate = sky1_audss_clk_mux_determine_rate,
+};
+
+static inline struct sky1_clk_divider *to_sky1_clk_divider(struct clk_divider *div)
+{
+ return container_of(div, struct sky1_clk_divider, div);
+}
+
+static unsigned long sky1_audss_clk_divider_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_divider *divider = to_clk_divider(hw);
+ struct sky1_clk_divider *sky1_div = to_sky1_clk_divider(divider);
+ unsigned int val;
+
+ regmap_read(sky1_div->regmap, sky1_div->offset, &val);
+ val = val >> divider->shift;
+ val &= clk_div_mask(divider->width);
+
+ return divider_recalc_rate(hw, parent_rate, val, divider->table,
+ divider->flags, divider->width);
+}
+
+static int sky1_audss_clk_divider_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct clk_divider *divider = to_clk_divider(hw);
+ struct sky1_clk_divider *sky1_div = to_sky1_clk_divider(divider);
+
+ /* if read only, just return current value */
+ if (divider->flags & CLK_DIVIDER_READ_ONLY) {
+ u32 val;
+
+ regmap_read(sky1_div->regmap, sky1_div->offset, &val);
+ val = val >> divider->shift;
+ val &= clk_div_mask(divider->width);
+
+ return divider_ro_determine_rate(hw, req, divider->table,
+ divider->width,
+ divider->flags, val);
+ }
+
+ return divider_determine_rate(hw, req, divider->table, divider->width,
+ divider->flags);
+}
+
+static int sky1_audss_clk_divider_set_rate(struct clk_hw *hw,
+ unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_divider *divider = to_clk_divider(hw);
+ struct sky1_clk_divider *sky1_div = to_sky1_clk_divider(divider);
+ int value;
+ unsigned long flags = 0;
+ u32 val;
+
+ value = divider_get_val(rate, parent_rate, divider->table,
+ divider->width, divider->flags);
+ if (value < 0)
+ return value;
+
+ if (divider->lock)
+ spin_lock_irqsave(divider->lock, flags);
+ else
+ __acquire(divider->lock);
+
+ if (divider->flags & CLK_DIVIDER_HIWORD_MASK) {
+ val = clk_div_mask(divider->width) << (divider->shift + 16);
+ } else {
+ regmap_read(sky1_div->regmap, sky1_div->offset, &val);
+ val &= ~(clk_div_mask(divider->width) << divider->shift);
+ }
+ val |= (u32)value << divider->shift;
+ regmap_write(sky1_div->regmap, sky1_div->offset, val);
+
+ if (divider->lock)
+ spin_unlock_irqrestore(divider->lock, flags);
+ else
+ __release(divider->lock);
+
+ return 0;
+}
+
+static const struct clk_ops sky1_audss_clk_divider_ops = {
+ .recalc_rate = sky1_audss_clk_divider_recalc_rate,
+ .determine_rate = sky1_audss_clk_divider_determine_rate,
+ .set_rate = sky1_audss_clk_divider_set_rate,
+};
+
+static inline struct sky1_clk_gate *to_sky1_clk_gate(struct clk_gate *gate)
+{
+ return container_of(gate, struct sky1_clk_gate, gate);
+}
+
+static void sky1_audss_clk_gate_endisable(struct clk_hw *hw, int enable)
+{
+ struct clk_gate *gate = to_clk_gate(hw);
+ struct sky1_clk_gate *sky1_gate = to_sky1_clk_gate(gate);
+ int set = gate->flags & CLK_GATE_SET_TO_DISABLE ? 1 : 0;
+ unsigned long flags = 0;
+ u32 reg;
+
+ set ^= enable;
+
+ if (gate->lock)
+ spin_lock_irqsave(gate->lock, flags);
+ else
+ __acquire(gate->lock);
+
+ if (gate->flags & CLK_GATE_HIWORD_MASK) {
+ reg = BIT(gate->bit_idx + 16);
+ if (set)
+ reg |= BIT(gate->bit_idx);
+ } else {
+ regmap_read(sky1_gate->regmap, sky1_gate->offset, ®);
+
+ if (set)
+ reg |= BIT(gate->bit_idx);
+ else
+ reg &= ~BIT(gate->bit_idx);
+ }
+
+ regmap_write(sky1_gate->regmap, sky1_gate->offset, reg);
+
+ if (gate->lock)
+ spin_unlock_irqrestore(gate->lock, flags);
+ else
+ __release(gate->lock);
+}
+
+static int sky1_audss_clk_gate_enable(struct clk_hw *hw)
+{
+ sky1_audss_clk_gate_endisable(hw, 1);
+
+ return 0;
+}
+
+static void sky1_audss_clk_gate_disable(struct clk_hw *hw)
+{
+ sky1_audss_clk_gate_endisable(hw, 0);
+}
+
+static int sky1_audss_clk_gate_is_enabled(struct clk_hw *hw)
+{
+ struct clk_gate *gate = to_clk_gate(hw);
+ struct sky1_clk_gate *sky1_gate = to_sky1_clk_gate(gate);
+ u32 reg;
+
+ regmap_read(sky1_gate->regmap, sky1_gate->offset, ®);
+
+ /* if a set bit disables this clk, flip it before masking */
+ if (gate->flags & CLK_GATE_SET_TO_DISABLE)
+ reg ^= BIT(gate->bit_idx);
+
+ reg &= BIT(gate->bit_idx);
+
+ return !!reg;
+}
+
+static const struct clk_ops sky1_audss_clk_gate_ops = {
+ .enable = sky1_audss_clk_gate_enable,
+ .disable = sky1_audss_clk_gate_disable,
+ .is_enabled = sky1_audss_clk_gate_is_enabled,
+};
+
+static struct clk_hw *sky1_audss_clk_register(struct device *dev,
+ const char *name,
+ const char * const *parent_names,
+ int num_parents,
+ struct regmap *regmap,
+ const u32 *mux_table,
+ struct muxdiv_cfg *mux_cfg,
+ struct muxdiv_cfg *div_cfg,
+ struct gate_cfg *gate_cfg,
+ unsigned long flags,
+ spinlock_t *lock)
+{
+ const struct clk_ops *sky1_mux_ops = NULL;
+ const struct clk_ops *sky1_div_ops = NULL;
+ const struct clk_ops *sky1_gate_ops = NULL;
+ struct clk_hw *hw = ERR_PTR(-ENOMEM);
+ struct sky1_clk_divider *sky1_div = NULL;
+ struct sky1_clk_gate *sky1_gate = NULL;
+ struct sky1_clk_mux *sky1_mux = NULL;
+
+ if (mux_cfg->offset >= 0) {
+ sky1_mux = devm_kzalloc(dev, sizeof(*sky1_mux), GFP_KERNEL);
+ if (!sky1_mux)
+ return ERR_PTR(-ENOMEM);
+
+ sky1_mux->mux.reg = NULL;
+ sky1_mux->mux.shift = mux_cfg->shift;
+ sky1_mux->mux.mask = BIT(mux_cfg->width) - 1;
+ sky1_mux->mux.flags = mux_cfg->flags;
+ sky1_mux->mux.table = mux_table;
+ sky1_mux->mux.lock = lock;
+ sky1_mux_ops = &sky1_audss_clk_mux_ops;
+ sky1_mux->regmap = regmap;
+ sky1_mux->offset = mux_cfg->offset;
+ }
+
+ if (div_cfg->offset >= 0) {
+ sky1_div = devm_kzalloc(dev, sizeof(*sky1_div), GFP_KERNEL);
+ if (!sky1_div)
+ return ERR_PTR(-ENOMEM);
+
+ sky1_div->div.reg = NULL;
+ sky1_div->div.shift = div_cfg->shift;
+ sky1_div->div.width = div_cfg->width;
+ sky1_div->div.flags = div_cfg->flags | CLK_DIVIDER_POWER_OF_TWO;
+ sky1_div->div.lock = lock;
+ sky1_div_ops = &sky1_audss_clk_divider_ops;
+ sky1_div->regmap = regmap;
+ sky1_div->offset = div_cfg->offset;
+ }
+
+ if (gate_cfg->offset >= 0) {
+ sky1_gate = devm_kzalloc(dev, sizeof(*sky1_gate), GFP_KERNEL);
+ if (!sky1_gate)
+ return ERR_PTR(-ENOMEM);
+
+ sky1_gate->gate.reg = NULL;
+ sky1_gate->gate.bit_idx = gate_cfg->shift;
+ sky1_gate->gate.flags = gate_cfg->flags;
+ sky1_gate->gate.lock = lock;
+ sky1_gate_ops = &sky1_audss_clk_gate_ops;
+ sky1_gate->regmap = regmap;
+ sky1_gate->offset = gate_cfg->offset;
+ }
+
+ hw = clk_hw_register_composite(dev, name, parent_names, num_parents,
+ sky1_mux ? &sky1_mux->mux.hw : NULL, sky1_mux_ops,
+ sky1_div ? &sky1_div->div.hw : NULL, sky1_div_ops,
+ sky1_gate ? &sky1_gate->gate.hw : NULL, sky1_gate_ops,
+ flags);
+ if (IS_ERR(hw)) {
+ dev_err(dev, "register %s clock failed with err = %ld\n",
+ name, PTR_ERR(hw));
+ return hw;
+ }
+
+ return hw;
+}
+
+static int sky1_audss_clks_get(struct sky1_audss_clks_priv *priv)
+{
+ const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data;
+ int i;
+
+ for (i = 0; i < devtype_data->clk_num; i++) {
+ priv->clks[i] = devm_clk_get(priv->dev, devtype_data->clk_names[i]);
+ if (IS_ERR(priv->clks[i]))
+ return dev_err_probe(priv->dev, PTR_ERR(priv->clks[i]),
+ "failed to get clock %s", devtype_data->clk_names[i]);
+ }
+
+ return 0;
+}
+
+static int sky1_audss_clks_enable(struct sky1_audss_clks_priv *priv)
+{
+ const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data;
+ int i, err;
+
+ for (i = 0; i < devtype_data->clk_num; i++) {
+ err = clk_prepare_enable(priv->clks[i]);
+ if (err) {
+ dev_err(priv->dev, "failed to enable clock %s\n",
+ devtype_data->clk_names[i]);
+ goto err_clks;
+ }
+ }
+
+ return 0;
+
+err_clks:
+ while (--i >= 0)
+ clk_disable_unprepare(priv->clks[i]);
+
+ return err;
+}
+
+static void sky1_audss_clks_disable(struct sky1_audss_clks_priv *priv)
+{
+ const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data;
+ int i;
+
+ for (i = 0; i < devtype_data->clk_num; i++)
+ clk_disable_unprepare(priv->clks[i]);
+}
+
+static int sky1_audss_clks_set_rate(struct sky1_audss_clks_priv *priv)
+{
+ const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data;
+ int i, err;
+
+ for (i = 0; i < devtype_data->clk_num; i++) {
+ err = clk_set_rate(priv->clks[i], devtype_data->clk_rate_default[i]);
+ if (err) {
+ dev_err(priv->dev, "failed to set clock rate %s\n",
+ devtype_data->clk_names[i]);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+/* register sky1 audio subsystem clocks */
+static int sky1_audss_clk_probe(struct platform_device *pdev)
+{
+ const struct sky1_audss_clks_devtype_data *devtype_data;
+ struct sky1_audss_clks_priv *priv;
+ struct device *dev = &pdev->dev;
+ struct clk_hw **clk_table;
+ void __iomem *base;
+ int i, ret;
+
+ devtype_data = device_get_match_data(dev);
+ if (!devtype_data)
+ return -ENODEV;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ spin_lock_init(&priv->lock);
+
+ priv->clk_data = devm_kzalloc(dev,
+ struct_size(priv->clk_data, hws, SKY1_AUDSS_NUM_CLKS),
+ GFP_KERNEL);
+ if (!priv->clk_data)
+ return -ENOMEM;
+
+ priv->clk_data->num = SKY1_AUDSS_NUM_CLKS;
+ clk_table = priv->clk_data->hws;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ priv->regmap_cru = devm_regmap_init_mmio(dev, base, &sky1_audss_regmap_config);
+ if (IS_ERR(priv->regmap_cru))
+ return dev_err_probe(dev, PTR_ERR(priv->regmap_cru),
+ "failed to initialize regmap\n");
+
+ priv->dev = dev;
+ priv->devtype_data = devtype_data;
+
+ priv->rst_noc = devm_reset_control_get_exclusive(dev, NULL);
+ if (IS_ERR(priv->rst_noc))
+ return dev_err_probe(dev, PTR_ERR(priv->rst_noc),
+ "failed to get audss noc reset");
+
+ reset_control_deassert(priv->rst_noc);
+
+ pm_runtime_get_noresume(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+
+ platform_set_drvdata(pdev, priv);
+
+ ret = sky1_audss_clks_get(priv);
+ if (ret)
+ goto err_pm;
+
+ ret = sky1_audss_clks_enable(priv);
+ if (ret) {
+ dev_err(dev, "failed to enable clocks\n");
+ goto err_pm;
+ }
+
+ ret = sky1_audss_clks_set_rate(priv);
+ if (ret) {
+ dev_err(dev, "failed to set clocks rate\n");
+ goto fail_clks_set;
+ }
+
+ /* audio_clk4 clock fixed divider */
+ clk_table[CLK_AUD_CLK4_DIV2] =
+ devm_clk_hw_register_fixed_factor(dev,
+ "audio_clk4_div2",
+ "audio_clk4",
+ 0,
+ 1, 2);
+ if (IS_ERR(clk_table[CLK_AUD_CLK4_DIV2])) {
+ ret = PTR_ERR(clk_table[CLK_AUD_CLK4_DIV2]);
+ dev_err(dev, "failed to register clock %d, ret:%d\n", CLK_AUD_CLK4_DIV2, ret);
+ goto fail_fixed_clk;
+ }
+
+ clk_table[CLK_AUD_CLK4_DIV4] =
+ devm_clk_hw_register_fixed_factor(dev,
+ "audio_clk4_div4",
+ "audio_clk4",
+ 0,
+ 1, 4);
+ if (IS_ERR(clk_table[CLK_AUD_CLK4_DIV4])) {
+ ret = PTR_ERR(clk_table[CLK_AUD_CLK4_DIV4]);
+ dev_err(dev, "failed to register clock %d, ret:%d\n", CLK_AUD_CLK4_DIV4, ret);
+ goto fail_fixed_clk;
+ }
+
+ /* audio_clk5 clock fixed divider */
+ clk_table[CLK_AUD_CLK5_DIV2] =
+ devm_clk_hw_register_fixed_factor(dev,
+ "audio_clk5_div2",
+ "audio_clk5",
+ 0,
+ 1, 2);
+ if (IS_ERR(clk_table[CLK_AUD_CLK5_DIV2])) {
+ ret = PTR_ERR(clk_table[CLK_AUD_CLK5_DIV2]);
+ dev_err(dev, "failed to register clock %d, ret:%d\n", CLK_AUD_CLK5_DIV2, ret);
+ goto fail_fixed_clk;
+ }
+
+ for (i = 0; i < devtype_data->clk_cfg_size; i++) {
+ clk_table[devtype_data->clk_cfg[i].id] =
+ sky1_audss_clk_register(dev,
+ devtype_data->clk_cfg[i].name,
+ devtype_data->clk_cfg[i].parent_names,
+ devtype_data->clk_cfg[i].num_parents,
+ priv->regmap_cru,
+ devtype_data->clk_cfg[i].mux_table,
+ devtype_data->clk_cfg[i].mux_cfg,
+ devtype_data->clk_cfg[i].div_cfg,
+ devtype_data->clk_cfg[i].gate_cfg,
+ devtype_data->clk_cfg[i].flags,
+ &priv->lock);
+ if (IS_ERR(clk_table[devtype_data->clk_cfg[i].id])) {
+ ret = PTR_ERR(clk_table[devtype_data->clk_cfg[i].id]);
+ dev_err(dev, "failed to register clock %d, ret:%d\n",
+ devtype_data->clk_cfg[i].id, ret);
+ goto fail_array_clk;
+ }
+ }
+
+ ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, priv->clk_data);
+ if (ret) {
+ dev_err(dev, "failed to add clock provider: %d\n", ret);
+ goto fail_register;
+ }
+
+ ret = sky1_audss_reset_controller_register(dev);
+ if (ret)
+ goto fail_register;
+
+ pm_runtime_put_sync(dev);
+
+ return 0;
+
+fail_register:
+fail_array_clk:
+ while (i--)
+ clk_hw_unregister_composite(clk_table[devtype_data->clk_cfg[i].id]);
+fail_fixed_clk:
+fail_clks_set:
+ pm_runtime_put_sync(dev);
+err_pm:
+ pm_runtime_disable(dev);
+ return ret;
+}
+
+static void sky1_audss_clk_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sky1_audss_clks_priv *priv = dev_get_drvdata(dev);
+ const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data;
+ int i = 0;
+
+ for (i = 0; i < devtype_data->clk_cfg_size; i++)
+ clk_hw_unregister_composite(priv->clk_data->hws[devtype_data->clk_cfg[i].id]);
+
+ if (!pm_runtime_status_suspended(dev))
+ pm_runtime_force_suspend(dev);
+
+ pm_runtime_disable(dev);
+}
+
+static int __maybe_unused sky1_audss_clk_runtime_suspend(struct device *dev)
+{
+ struct sky1_audss_clks_priv *priv = dev_get_drvdata(dev);
+ const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data;
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ for (i = 0; i < devtype_data->reg_save_size; i++)
+ regmap_read(priv->regmap_cru,
+ devtype_data->reg_save[i][0], &devtype_data->reg_save[i][1]);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ sky1_audss_clks_disable(priv);
+
+ return reset_control_assert(priv->rst_noc);
+}
+
+static int __maybe_unused sky1_audss_clk_runtime_resume(struct device *dev)
+{
+ struct sky1_audss_clks_priv *priv = dev_get_drvdata(dev);
+ const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data;
+ unsigned long flags;
+ int i, ret;
+
+ ret = reset_control_deassert(priv->rst_noc);
+ if (ret)
+ return ret;
+
+ ret = sky1_audss_clks_enable(priv);
+ if (ret) {
+ dev_err(dev, "failed to enable clocks\n");
+ return ret;
+ }
+
+ spin_lock_irqsave(&priv->lock, flags);
+ for (i = 0; i < devtype_data->reg_save_size; i++)
+ regmap_write(priv->regmap_cru,
+ devtype_data->reg_save[i][0], devtype_data->reg_save[i][1]);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+static const struct dev_pm_ops sky1_audss_clk_pm_ops = {
+ SET_RUNTIME_PM_OPS(sky1_audss_clk_runtime_suspend,
+ sky1_audss_clk_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static const struct sky1_audss_clks_devtype_data sky1_devtype_data = {
+ .reg_save = sky1_reg_save,
+ .reg_save_size = ARRAY_SIZE(sky1_reg_save),
+ .clk_names = sky1_audss_clk_names,
+ .clk_num = ARRAY_SIZE(sky1_audss_clk_names),
+ .clk_rate_default = sky1_clk_rate_default,
+ .clk_cfg = sky1_audss_clks,
+ .clk_cfg_size = ARRAY_SIZE(sky1_audss_clks),
+};
+
+static const struct of_device_id sky1_audss_clk_of_match[] = {
+ { .compatible = "cix,sky1-audss-cru", .data = &sky1_devtype_data, },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, sky1_audss_clk_of_match);
+
+static struct platform_driver sky1_audss_clk_driver = {
+ .probe = sky1_audss_clk_probe,
+ .remove = sky1_audss_clk_remove,
+ .driver = {
+ .name = "sky1-audss-clk",
+ .suppress_bind_attrs = true,
+ .of_match_table = sky1_audss_clk_of_match,
+ .pm = &sky1_audss_clk_pm_ops,
+ },
+};
+module_platform_driver(sky1_audss_clk_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Joakim Zhang <joakim.zhang@cixtech.com>");
+MODULE_DESCRIPTION("Cixtech Sky1 Audio Subsystem Clock Controller Driver");
--
2.50.1
^ permalink raw reply related
* [PATCH v5 4/4] arm64: dts: cix: sky1: add audss cru
From: joakim.zhang @ 2026-06-22 2:25 UTC (permalink / raw)
To: mturquette, sboyd, bmasney, robh, krzk+dt, conor+dt, p.zabel,
gary.yang
Cc: cix-kernel-upstream, linux-clk, devicetree, linux-kernel,
linux-arm-kernel, Joakim Zhang
In-Reply-To: <20260622022520.3127103-1-joakim.zhang@cixtech.com>
From: Joakim Zhang <joakim.zhang@cixtech.com>
Add the AUDSS CRU device node providing clocks and software resets
for audio subsystem peripherals.
Signed-off-by: Joakim Zhang <joakim.zhang@cixtech.com>
---
arch/arm64/boot/dts/cix/sky1.dtsi | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/arch/arm64/boot/dts/cix/sky1.dtsi b/arch/arm64/boot/dts/cix/sky1.dtsi
index bb5cfb1f2113..f125f92d64aa 100644
--- a/arch/arm64/boot/dts/cix/sky1.dtsi
+++ b/arch/arm64/boot/dts/cix/sky1.dtsi
@@ -6,6 +6,10 @@
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/clock/cix,sky1.h>
+#include <dt-bindings/clock/cix,sky1-audss-clock.h>
+#include <dt-bindings/reset/cix,sky1-system-control.h>
+#include <dt-bindings/reset/cix,sky1-s5-system-control.h>
+#include <dt-bindings/reset/cix,sky1-audss-reset.h>
#include "sky1-power.h"
/ {
@@ -488,6 +492,21 @@ mbox_pm2ap: mailbox@65a0080 {
cix,mbox-dir = "rx";
};
+ audss_cru: clock-controller@7110000 {
+ compatible = "cix,sky1-audss-cru";
+ reg = <0x0 0x07110000 0x0 0x10000>;
+ #clock-cells = <1>;
+ #reset-cells = <1>;
+ clocks = <&scmi_clk CLK_TREE_AUDIO_CLK0>,
+ <&scmi_clk CLK_TREE_AUDIO_CLK2>,
+ <&scmi_clk CLK_TREE_AUDIO_CLK4>,
+ <&scmi_clk CLK_TREE_AUDIO_CLK5>;
+ clock-names = "x8k", "x11k", "sys", "48m";
+ power-domains = <&smc_devpd SKY1_PD_AUDIO>;
+ resets = <&s5_syscon SKY1_AUDIO_HIFI5_NOC_RESET_N>;
+ status = "okay";
+ };
+
mbox_sfh2ap: mailbox@8090000 {
compatible = "cix,sky1-mbox";
reg = <0x0 0x08090000 0x0 0x10000>;
--
2.50.1
^ permalink raw reply related
* [PATCH v5 0/4] Add Cix Sky1 AUDSS clock and reset support
From: joakim.zhang @ 2026-06-22 2:25 UTC (permalink / raw)
To: mturquette, sboyd, bmasney, robh, krzk+dt, conor+dt, p.zabel,
gary.yang
Cc: cix-kernel-upstream, linux-clk, devicetree, linux-kernel,
linux-arm-kernel, Joakim Zhang
From: Joakim Zhang <joakim.zhang@cixtech.com>
The Cix Sky1 Audio Subsystem (AUDSS) groups audio-related blocks such as
HDA, I2S, DSP, DMA, mailboxes, watchdog and timer behind one Clock and
Reset Unit (CRU). The CRU is a single MMIO register block that provides
clock muxing, gating and block-level software reset lines for those
peripherals.
Clock and reset support are submitted in one series because they belong
to the same hardware block and share one devicetree node
(cix,sky1-audss-cru). The binding, clock indices and reset indices are
defined together; the clock driver maps the CRU and instantiates the
reset controller as an auxiliary driver on that node. Splitting clk and
reset across separate series would leave neither side self-contained: the
DTS node needs both providers, and the reset driver has no standalone
probe path without the clock driver.
---
ChangeLogs:
v4->v5:
* refactor the driver, using platform_driver for clk and auxiliary_driver
for reset.
v3->v4:
* move both power domain and resets into parset node (audss_cru)
* remove "simple-mfd", and change to populate the child node
* cix,sky1-audss.h -> cix,sky1-audss-clock.h
v2->v3:
* clk part:
* devm_reset_control_get()->devm_reset_control_get_exclusive()
* assert noc reset from suspend
* clock parents changes from 6 to 4, and rename the clock names,
explain more about this: confirm with our designer, In fact,
there are 6 clock sources going into the audio subsystem. audio_clk1
and audio_clk3 are redundant in design and are not actually needed
in practice, so they are not shown here.
* refine clocks and clock-names property
* add detailed description of clocks
* drop parent node from clk binding
* drop define AUDSS_MAX_CLKS
* reset part:
* rename reset signal macro, remove _N
* drop SKY1_AUDSS_SW_RESET_NUM
* switching to compatible-style of defining subnodes in parent schema
v1->v2:
* remove audss_rst device node since it doesn't has resource, and
move to reset-sky1.c driver.
* remove hda related which would be sent after this patch set accepted
* soc componnet is okay by default from dtsi
* fix for audss clk driver:
* remove "comment "Clock options for Cixtech audss:""
* add select MFD_SYSCON
* move lock and clk_data into struct sky1_audss_clks_priv
* const char *name -> const char * const * name
* remove CLK_GET_RATE_NOCACHE
* divicer -> divider
* Reverse Christmas tree order
* return reg ? 1 : 0; -> return !!reg;
* return ERR_CAST(hw); -> return hw;
* of_device_get_match_data(dev) -> device_get_match_data()
* add lock from runtime_suspend/resume
* loop to more mailing lists
Joakim Zhang (4):
dt-bindings: soc: cix: add sky1 audss cru controller
clk: cix: add sky1 audss clock controller
reset: cix: add sky1 audss auxiliary reset driver
arm64: dts: cix: sky1: add audss cru
.../bindings/soc/cix/cix,sky1-audss-cru.yaml | 92 ++
arch/arm64/boot/dts/cix/sky1.dtsi | 19 +
drivers/clk/Kconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/cix/Kconfig | 16 +
drivers/clk/cix/Makefile | 3 +
drivers/clk/cix/clk-sky1-audss.c | 1205 +++++++++++++++++
drivers/reset/Kconfig | 14 +
drivers/reset/Makefile | 1 +
drivers/reset/reset-sky1-audss.c | 192 +++
.../dt-bindings/clock/cix,sky1-audss-clock.h | 60 +
.../dt-bindings/reset/cix,sky1-audss-reset.h | 25 +
12 files changed, 1629 insertions(+)
create mode 100644 Documentation/devicetree/bindings/soc/cix/cix,sky1-audss-cru.yaml
create mode 100644 drivers/clk/cix/Kconfig
create mode 100644 drivers/clk/cix/Makefile
create mode 100644 drivers/clk/cix/clk-sky1-audss.c
create mode 100644 drivers/reset/reset-sky1-audss.c
create mode 100644 include/dt-bindings/clock/cix,sky1-audss-clock.h
create mode 100644 include/dt-bindings/reset/cix,sky1-audss-reset.h
--
2.50.1
^ permalink raw reply
* Re: Re: [PATCH net-next v8 3/6] net: stmmac: eic7700: make RGMII delay properties optional
From: 李志 @ 2026-06-22 2:19 UTC (permalink / raw)
To: Andrew Lunn
Cc: Maxime Chevallier, devicetree, andrew+netdev, davem, edumazet,
kuba, robh, krzk+dt, conor+dt, netdev, pabeni, mcoquelin.stm32,
alexandre.torgue, rmk+kernel, pjw, palmer, aou, alex, linux-riscv,
linux-stm32, linux-arm-kernel, linux-kernel, ningyu, linmin,
pinkesh.vaghela, pritesh.patel, weishangjuan, horms, lee
In-Reply-To: <30229cfe-b395-4d0f-81ef-eb780ac26599@lunn.ch>
> -----Original Messages-----
> From: "Andrew Lunn" <andrew@lunn.ch>
> Send time:Saturday, 13/06/2026 15:48:46
> To: "Maxime Chevallier" <maxime.chevallier@bootlin.com>
> Cc: lizhi2@eswincomputing.com, devicetree@vger.kernel.org, andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com, kuba@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, netdev@vger.kernel.org, pabeni@redhat.com, mcoquelin.stm32@gmail.com, alexandre.torgue@foss.st.com, rmk+kernel@armlinux.org.uk, pjw@kernel.org, palmer@dabbelt.com, aou@eecs.berkeley.edu, alex@ghiti.fr, linux-riscv@lists.infradead.org, linux-stm32@st-md-mailman.stormreply.com, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, ningyu@eswincomputing.com, linmin@eswincomputing.com, pinkesh.vaghela@einfochips.com, pritesh.patel@einfochips.com, weishangjuan@eswincomputing.com, horms@kernel.org, lee@kernel.org
> Subject: Re: [PATCH net-next v8 3/6] net: stmmac: eic7700: make RGMII delay properties optional
>
> On Wed, Jun 10, 2026 at 10:26:50AM +0200, Maxime Chevallier wrote:
> > Hi,
> >
> > On 6/10/26 03:29, lizhi2@eswincomputing.com wrote:
> > > From: Zhi Li <lizhi2@eswincomputing.com>
> > >
> > > Make rx-internal-delay-ps and tx-internal-delay-ps optional in the
> > > EIC7700 DWMAC driver.
> > >
> > > The driver previously required both properties to be present and would
> > > fail probe when they were missing. This restricts valid hardware
> > > configurations where RGMII timing is instead provided by the PHY or
> > > board design.
> > >
> > > Update the driver to treat missing delay properties as zero delay,
> > > allowing systems without explicit MAC-side delay tuning to operate
> > > correctly.
> > >
> > > This aligns the driver behavior with the updated device tree binding
> > > and provides a safe default configuration when MAC-side delay
> > > programming is not required.
> > >
> > > Signed-off-by: Zhi Li <lizhi2@eswincomputing.com>
> > > ---
> > > drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c | 6 ------
> > > 1 file changed, 6 deletions(-)
> > >
> > > diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c
> > > index 4ac979d874d6..ec99b597aeaf 100644
> > > --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c
> > > +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c
> > > @@ -165,9 +165,6 @@ static int eic7700_dwmac_probe(struct platform_device *pdev)
> > > dwc_priv->eth_clk_dly_param &= ~EIC7700_ETH_RX_ADJ_DELAY;
> > > dwc_priv->eth_clk_dly_param |=
> > > FIELD_PREP(EIC7700_ETH_RX_ADJ_DELAY, val);
> > > - } else {
> > > - return dev_err_probe(&pdev->dev, -EINVAL,
> > > - "missing required property rx-internal-delay-ps\n");
> > > }
> > >
> > > /* Read tx-internal-delay-ps and update tx_clk delay */
> > > @@ -187,9 +184,6 @@ static int eic7700_dwmac_probe(struct platform_device *pdev)
> > > dwc_priv->eth_clk_dly_param &= ~EIC7700_ETH_TX_ADJ_DELAY;
> > > dwc_priv->eth_clk_dly_param |=
> > > FIELD_PREP(EIC7700_ETH_TX_ADJ_DELAY, val);
> > > - } else {
> > > - return dev_err_probe(&pdev->dev, -EINVAL,
> > > - "missing required property tx-internal-delay-ps\n");
> > > }
> >
> > I think then you need to handle RGMII, RGMII_ID, RGMII_RXID and RGMII_TXID,
> > by using default delays for these (usually around 2ns), as here all delays
> > will be set to 0, regardless of the RGMII mode in use.
>
> No. By default, the MAC adds 0ns delay, and passes the phy-mode to the
> PHY. It will then add the 2ns delay. It is possible to use the
> tx-internal-delay-ps and rx-internal-delay-ps in the MAC to add small
> tuning delays, but not the full 2ns.
>
> https://elixir.bootlin.com/linux/v6.15/source/Documentation/devicetree/bindings/net/ethernet-controller.yaml#L287
>
Thanks for the earlier discussion and for helping clarify the eth0
design.
I'm preparing a v9 of the series. The next revision will address the
issues reported by Sashiko review, mainly DT binding schema and DTS
warnings.
Before I post v9, I'd like to check whether you have any concerns or
suggestions regarding the driver changes.
Thanks,
Zhi
^ permalink raw reply
* Re: [PATCH v3 1/5] dmaengine: sun6i-dma: Refactor to support A733 interrupt and register handling
From: sashiko-bot @ 2026-06-22 1:54 UTC (permalink / raw)
To: Yuanshen Cao
Cc: Frank.Li, vkoul, robh, dmaengine, conor+dt, devicetree,
linux-sunxi
In-Reply-To: <20260622-sun60i-a733-dma-v3-1-f697ef296cbc@gmail.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] Integer division truncates the channel check in the interrupt handler, causing interrupts for channels >= 8 to be completely ignored on SoCs where the number of channels is not a multiple of 8.
- [High] Incorrect calculation of the physical channel pointer in the interrupt handler aliases upper hardware channels to lower software channels, causing Use-After-Free and data corruption.
- [High] A Time-of-Check to Time-of-Use (TOCTOU) race condition in `sun6i_kill_tasklet` allows a concurrent tasklet to re-enable interrupts after they were intentionally disabled, causing an unhandled interrupt storm after the IRQ is freed.
--
commit 4e47cdc0e212cbdd5c17510fa923b572eb14fb54
Author: Yuanshen Cao <alex.caoys@gmail.com>
dmaengine: sun6i-dma: Refactor to support A733 interrupt and register handling
Refactor to support the Allwinner A733 DMA controller by moving interrupt
handling and register dumping functions into the sun6i_dma_config structure.
This allows a polymorphic approach determined by hardware configuration
during device probing.
> diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
> index a9a254dbf8cb9..ef3052c4ab363 100644
> --- a/drivers/dma/sun6i-dma.c
> +++ b/drivers/dma/sun6i-dma.c
> @@ -347,6 +352,26 @@ static void sun6i_set_mode_h6(u32 *p_cfg, s8 src_mode, s8 dst_mode)
[ ... ]
> +static void sun6i_write_irq_en(struct sun6i_dma_dev *sdev, u32 irq_reg, u32 irq_val)
> +{
> + writel(irq_val, sdev->base + DMA_IRQ_EN(irq_reg));
> +}
[Severity: High]
This is a pre-existing issue, but does disabling interrupts leave a race
condition?
In sun6i_kill_tasklet(), hardware interrupts are disabled via write_irq_en(0)
before tasklet_kill() is called:
sun6i_kill_tasklet()
for (i = 0; i < ...; i++)
sdev->cfg->write_irq_en(sdev, i, 0);
atomic_inc(&sdev->tasklet_shutdown);
devm_free_irq(sdev->slave.dev, sdev->irq, sdev);
tasklet_kill(&sdev->task);
If sun6i_dma_tasklet() runs concurrently and calls sun6i_dma_start_desc(),
could it perform a read-modify-write on the IRQ_EN register without a lock?
This could re-enable the interrupt after sun6i_kill_tasklet() explicitly
disabled it. When devm_free_irq() is called, an active DMA channel might
then assert an unhandled IRQ, leading to an interrupt storm.
[ ... ]
> @@ -549,14 +574,14 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
> u32 status;
>
> for (i = 0; i < sdev->num_pchans / DMA_IRQ_CHAN_NR; i++) {
[Severity: High]
This is a pre-existing issue, but does integer division truncate the channel
check here?
If num_pchans is not a multiple of 8 (like 12 on H3), dividing by 8 yields 1.
This would mean the loop only checks channels 0-7 and ignores the second
interrupt register for channels 8-11.
Could this cause unacknowledged level-triggered interrupts from those upper
channels, leading to an interrupt storm?
> - status = readl(sdev->base + DMA_IRQ_STAT(i));
> + status = sdev->cfg->read_irq_stat(sdev, i);
> if (!status)
> continue;
>
> dev_dbg(sdev->slave.dev, "DMA irq status %s: 0x%x\n",
> str_high_low(i), status);
>
> - writel(status, sdev->base + DMA_IRQ_STAT(i));
> + sdev->cfg->write_irq_stat(sdev, i, status);
>
> for (j = 0; (j < DMA_IRQ_CHAN_NR) && status; j++) {
> pchan = sdev->pchans + j;
[Severity: High]
This isn't a bug introduced by this patch, but does the physical channel
pointer calculation alias upper hardware channels to lower software channels?
The calculation sdev->pchans + j ignores the register index i.
For upper channels (where i = 1, meaning channels 8-15), would the handler
map the active hardware interrupt back to software channels 0-7?
It seems this might cause the driver to process and complete descriptors for
the wrong channels while the hardware is still writing to them, potentially
causing data corruption and use-after-free conditions.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260622-sun60i-a733-dma-v3-0-f697ef296cbc@gmail.com?part=1
^ permalink raw reply
* Re: [PATCH v3 5/5] dmaengine: sun6i-dma: Add support for Allwinner A733 DMA controller
From: sashiko-bot @ 2026-06-22 1:52 UTC (permalink / raw)
To: Yuanshen Cao
Cc: Frank.Li, vkoul, dmaengine, linux-sunxi, robh, conor+dt,
devicetree
In-Reply-To: <20260622-sun60i-a733-dma-v3-5-f697ef296cbc@gmail.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] Hardcoded loop bound in `sun6i_kill_tasklet` leads to out-of-bounds MMIO register writes.
Pre-existing issues:
- [Critical] Incorrect physical channel indexing in `sun6i_dma_interrupt` misroutes hardware interrupts to the wrong software channels.
- [High] Integer division truncation in IRQ register loops silently drops interrupts for leftover channels.
- [High] A TOCTOU race exists between `sun6i_dma_interrupt` and `sun6i_dma_terminate_all`, leading to a NULL pointer dereference.
--
commit 901a8f4f5bfa274478e14bdc97eb09faba518f9f
Author: Yuanshen Cao <alex.caoys@gmail.com>
dmaengine: sun6i-dma: Add support for Allwinner A733 DMA controller
Support Allwinner A733 DMA controller. Define new register offsets,
bitfield mappings and dma_config required for the A733, which slightly
differs from the older sun6i DMA controllers.
> diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
> index 196a0d73b2212..4808015934ccb 100644
> --- a/drivers/dma/sun6i-dma.c
> +++ b/drivers/dma/sun6i-dma.c
> @@ -257,6 +268,23 @@ static inline void sun6i_dma_dump_com_regs(struct sun6i_dma_dev *sdev)
[ ... ]
> +static inline void sun6i_dma_dump_com_regs_a733(struct sun6i_dma_dev *sdev)
> +{
> + int i;
> +
> + for (i = 0; i < sdev->num_pchans / sdev->cfg->num_channels_per_reg; i++) {
[Severity: High]
This is a pre-existing issue, but I noticed this loop limit might cause integer
division truncation.
If the total number of channels is not an exact multiple of the channels
per register, the fractional remainder is truncated. For example, if a
platform has 12 channels and 8 per register, the loop only iterates once,
completely ignoring the second register.
Does this mean interrupts for the leftover channels are never read or
cleared, leading to an unhandled interrupt storm? Should this use
DIV_ROUND_UP instead? This buggy logic also seems to be present in
sun6i_dma_interrupt().
> + dev_dbg(sdev->slave.dev, "Common register:\n"
[ ... ]
> @@ -1339,6 +1398,33 @@ static struct sun6i_dma_config sun50i_h6_dma_cfg = {
[ ... ]
> +static struct sun6i_dma_config sun60i_a733_dma_cfg = {
> + .clock_autogate_enable = sun6i_enable_clock_autogate_h3,
[ ... ]
> + .dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
> + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
> + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES),
> + .num_channels_per_reg = DMA_IRQ_CHAN_NR_A733,
[Severity: Critical]
This is a pre-existing issue, but setting num_channels_per_reg to 1 for
the A733 controller appears to completely break the DMA controller due
to an issue in sun6i_dma_interrupt().
In sun6i_dma_interrupt(), the physical channel is resolved as:
pchan = sdev->pchans + j;
Since num_channels_per_reg is 1, j is always 0. Does this route every
hardware interrupt to software channel 0, regardless of the actual
triggering channel? Should the calculation instead be updated to properly
account for the register index:
pchan = sdev->pchans + i * sdev->cfg->num_channels_per_reg + j;
[Severity: High]
I also noticed an issue in sun6i_kill_tasklet() that might be triggered
by this configuration.
sun6i_kill_tasklet() calculates its iteration bounds using:
for (i = 0; i < DMA_MAX_CHANNELS / sdev->cfg->num_channels_per_reg; i++)
sdev->cfg->write_irq_en(sdev, i, 0);
With DMA_MAX_CHANNELS set to 16 and num_channels_per_reg set to 1, this
loop will unconditionally execute 16 times. If the dynamically initialized
channel count for the SoC is fewer than 16, will these writes target
unmapped peripheral memory space and potentially trigger a synchronous
external abort? Should the loop bound depend on the actual initialized
channel count (sdev->num_pchans) instead?
> + .has_mbus_clk = true,
> +};
[Severity: High]
This is a pre-existing issue, but could there be a TOCTOU race condition
between sun6i_dma_interrupt() and sun6i_dma_terminate_all() that leads to
a NULL pointer dereference?
In sun6i_dma_terminate_all(), the channel is stopped and pointers are cleared:
vchan->phy = NULL;
pchan->vchan = NULL;
pchan->desc = NULL;
pchan->done = NULL;
However, in sun6i_dma_interrupt(), the pointers are accessed like this:
pchan = sdev->pchans + j;
vchan = pchan->vchan;
if (vchan && (status & vchan->irq_type)) {
if (vchan->cyclic) {
vchan_cyclic_callback(&pchan->desc->vd);
} else {
spin_lock(&vchan->vc.lock);
vchan_cookie_complete(&pchan->desc->vd);
Since the interrupt handler checks vchan = pchan->vchan outside the lock,
could a concurrent call to sun6i_dma_terminate_all() clear pchan->desc
before it is dereferenced inside the interrupt handler, resulting in a
near-NULL pointer dereference?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260622-sun60i-a733-dma-v3-0-f697ef296cbc@gmail.com?part=5
^ permalink raw reply
* Re: [PATCH v3 3/5] dmaengine: sun6i-dma: Add num_channels_per_reg for flexible interrupt mapping
From: sashiko-bot @ 2026-06-22 1:49 UTC (permalink / raw)
To: Yuanshen Cao
Cc: conor+dt, devicetree, linux-sunxi, vkoul, robh, Frank.Li,
dmaengine
In-Reply-To: <20260622-sun60i-a733-dma-v3-3-f697ef296cbc@gmail.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Loop bound truncation in `sun6i_dma_interrupt` ignores higher physical channels.
- [High] The physical channel index computation in `sun6i_dma_interrupt` completely ignores the register offset.
--
commit 60cc97cd5f987f5b25e3c1a483094c243658c5e5
Author: Yuanshen Cao <alex.caoys@gmail.com>
dmaengine: sun6i-dma: Add num_channels_per_reg for flexible interrupt mapping
The patch introduces num_channels_per_reg to sun6i_dma_config to support
different SoC interrupt structures where channels per register may differ.
It replaces hardcoded IRQ register calculations and sets DMA_MAX_CHANNELS
to 16.
> diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
> index 9984b9033cbb9..196a0d73b2212 100644
> --- a/drivers/dma/sun6i-dma.c
> +++ b/drivers/dma/sun6i-dma.c
[ ... ]
> @@ -575,7 +576,7 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
> int i, j, ret = IRQ_NONE;
> u32 status;
>
> - for (i = 0; i < sdev->num_pchans / DMA_IRQ_CHAN_NR; i++) {
> + for (i = 0; i < sdev->num_pchans / sdev->cfg->num_channels_per_reg; i++) {
[Severity: High]
This is a pre-existing issue, but does this loop condition truncate the
channel count if it isn't an exact multiple of the channels per register?
For example, if an SoC has 12 channels and 8 channels per register, integer
division (12 / 8) yields 1, causing the loop to only process the first
register (channels 0-7) and skip the second register (channels 8-11).
If a transfer completes on a higher channel, could this result in an
unhandled interrupt storm since the status is never read or cleared? Should
this use DIV_ROUND_UP(sdev->num_pchans, sdev->cfg->num_channels_per_reg)?
> status = sdev->cfg->read_irq_stat(sdev, i);
> if (!status)
> continue;
> @@ -585,7 +586,7 @@ static irqreturn_t sun6i_dma_interrupt(int irq, void *dev_id)
>
> sdev->cfg->write_irq_stat(sdev, i, status);
>
> - for (j = 0; (j < DMA_IRQ_CHAN_NR) && status; j++) {
> + for (j = 0; (j < sdev->cfg->num_channels_per_reg) && status; j++) {
> pchan = sdev->pchans + j;
[Severity: High]
This is a pre-existing issue, but does this physical channel lookup ignore
the register offset from the outer loop?
When processing the second interrupt register (i = 1, channels 8-15), the
inner loop accesses pchan indices 0 through 7 instead of offsetting by
i * num_channels_per_reg.
Could a legitimate hardware interrupt on channel 8 spuriously trigger the
completion callback for the client mapped to channel 0, while channel 8's
client waits indefinitely?
Should the lookup be:
pchan = sdev->pchans + (i * sdev->cfg->num_channels_per_reg) + j;
> vchan = pchan->vchan;
> if (vchan && (status & vchan->irq_type)) {
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260622-sun60i-a733-dma-v3-0-f697ef296cbc@gmail.com?part=3
^ permalink raw reply
* Re: [PATCH v3 4/5] dt-bindings: dmaengine: sun50i-a64-dma: Add allwinner,sun60i-a733-dma compatible string
From: sashiko-bot @ 2026-06-22 1:49 UTC (permalink / raw)
To: Yuanshen Cao
Cc: conor+dt, dmaengine, devicetree, linux-sunxi, robh, vkoul,
Frank.Li
In-Reply-To: <20260622-sun60i-a733-dma-v3-4-f697ef296cbc@gmail.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [Critical] The interrupt handler reads the status for higher physical channels but incorrectly applies completions to channels 0-7, causing cross-channel corruption and dropping interrupts.
- [High] The interrupt handler loop bound truncates remainders, skipping the last interrupt register for SoCs whose channel count is not a multiple of `num_channels_per_reg`.
--
commit 949f13d1f9dc8ae54064e512fd45f644694422c7
Author: Yuanshen Cao <alex.caoys@gmail.com>
dt-bindings: dmaengine: sun50i-a64-dma: Add allwinner,sun60i-a733-dma compatible string
Add allwinner,sun60i-a733-dma to the list of compatible strings for the
sun50i-a64-dma dtbinding documentation.
> diff --git a/Documentation/devicetree/bindings/dma/allwinner,sun50i-a64-dma.yaml b/Documentation/devicetree/bindings/dma/allwinner,sun50i-a64-dma.yaml
> index c3e14eb6cfff9..1cc3304b74149 100644
> --- a/Documentation/devicetree/bindings/dma/allwinner,sun50i-a64-dma.yaml
> +++ b/Documentation/devicetree/bindings/dma/allwinner,sun50i-a64-dma.yaml
> @@ -25,6 +25,7 @@ properties:
> - allwinner,sun50i-a64-dma
> - allwinner,sun50i-a100-dma
> - allwinner,sun50i-h6-dma
> + - allwinner,sun60i-a733-dma
[Severity: Critical]
This is a pre-existing issue, but does the interrupt handler in
sun6i_dma_interrupt() correctly handle the physical channel calculation?
Looking at drivers/dma/sun6i-dma.c:sun6i_dma_interrupt():
pchan = sdev->pchans + j;
The physical channel is derived from the inner loop index j alone, completely
ignoring the outer loop register index i. For the newly added sun60i-a733
(where num_channels_per_reg is 1) or SoCs with more than 8 channels, an
interrupt on a higher channel will incorrectly apply the completion to channel
0.
Can this corrupt the state by signaling spurious completions for active
transfers on lower channels while permanently hanging the actual completing
channels?
[Severity: High]
This is also a pre-existing issue. Is the interrupt handler skipping registers
for SoCs whose channel count is not a multiple of num_channels_per_reg?
Looking at the loop condition in sun6i_dma_interrupt():
for (i = 0; i < sdev->num_pchans / sdev->cfg->num_channels_per_reg; i++) {
The loop bound uses integer division. For SoCs like the H3 (which has 12
channels and 8 channels per register), 12 / 8 evaluates to 1.
Will this cause the loop to only check the first register (channels 0-7) and
completely skip the second register (channels 8-11), permanently dropping
interrupts for channels 8 and above? Would using DIV_ROUND_UP() be more
appropriate here?
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260622-sun60i-a733-dma-v3-0-f697ef296cbc@gmail.com?part=4
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox