From: "Stefan Dösinger" <stefandoesinger@gmail.com>
To: Michael Turquette <mturquette@baylibre.com>,
Stephen Boyd <sboyd@kernel.org>, Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Philipp Zabel <p.zabel@pengutronix.de>
Cc: linux-clk@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
"Stefan Dösinger" <stefandoesinger@gmail.com>
Subject: [PATCH RFC v2 3/4] clk: zte: Introduce a driver for zx297520v3 matrix clocks and resets.
Date: Mon, 11 May 2026 00:49:52 +0300 [thread overview]
Message-ID: <20260511-zx29clk-v2-3-29f0edc300f5@gmail.com> (raw)
In-Reply-To: <20260511-zx29clk-v2-0-29f0edc300f5@gmail.com>
This controls the CPU, DSP, DDR RAM, ethernet, SDIO controllers and a
few more devices. It also contains a number of clock gates to pass
clock signals down to the next controller.
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
drivers/clk/zte/clk-zx297520v3.c | 215 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 214 insertions(+), 1 deletion(-)
diff --git a/drivers/clk/zte/clk-zx297520v3.c b/drivers/clk/zte/clk-zx297520v3.c
index aa304dd34b7b..d4b683cb6354 100644
--- a/drivers/clk/zte/clk-zx297520v3.c
+++ b/drivers/clk/zte/clk-zx297520v3.c
@@ -266,7 +266,7 @@ static int zx297520v3_pll(struct device *dev, void __iomem *base, const char *na
/* These are the fractionals of the PLLs I have seen. There should be a better way to
* generate them than hardcode the list.
*/
- static const unsigned int pll_fract[] = {2, 3, 4, 5, 6, 8, 12, 26};
+ static const unsigned int pll_fract[] = {2, 3, 4, 5, 6, 8, 12, 16, 26};
unsigned long ref, refdiv, fbdiv, vco, postdiv1, postdiv2, freq;
struct clk_hw *hw;
@@ -578,6 +578,219 @@ static struct platform_driver clk_zx297520v3_topclk = {
};
module_platform_driver(clk_zx297520v3_topclk);
+static const char * const cpu_sel[] = {
+ "osc26m",
+ "mpll", /* 624 MHz */
+ "mpll_d2", /* 312 MHz */
+ "mpll_d4", /* 156 MHz */
+};
+
+static const char * const sd0_sel[] = {
+ "osc26m",
+ "mpll_d4", /* 156 MHz */
+ "gpll_d2", /* 100 MHz */
+ "mpll_d8", /* 78 MHz */
+ "gpll_d4", /* 50 MHz */
+ "gpll_d8", /* 25 MHz */
+};
+
+static const char * const sd1_sel[] = {
+ "osc26m",
+ "gpll_d2", /* 100 MHz */
+ "mpll_d8", /* 78 MHz */
+ "gpll_d4", /* 50 MHz */
+ "mpll_d16", /* 39 MHz */
+ "gpll_d8", /* 25 MHz */
+};
+
+static const char * const nand_sel[] = {
+ "mpll_d4", /* 156 MHz */
+ "osc26m",
+};
+
+static const char * const edcp_sel[] = {
+ "osc26m",
+ "mpll_d4", /* 156 MHz */
+ "mpll_d5", /* 124.8 MHz */
+ "mpll_d6", /* 104 MHz */
+};
+
+static const char * const tdm_sel[] = {
+ "osc26m",
+ "dpll_d4", /* 122.88 MHz */
+ "mpll_d6", /* 104 MHz */
+};
+
+static const struct zx297520v3_composite matrix_clocks[] = {
+ /* Both 0x24 and 0x28 bits 1 and 2 stop the CPU. There is also a bit in topclk+0x138, which
+ * ZTE's uboot calls "A53 reset", which also stops the CPU. I can't really tell the
+ * difference between matrix+28 and top+138. The clock can be disabled and enabled from the
+ * Cortex M0 and it will nicely stop and restart the A53, retaining all state.
+ *
+ * 0x50, bits 0-3 have the DDR clock. A lot of DDR gates and resets are in 0x100.
+ */
+ ZX_CLK_CRIT(CPU, 0x28, 1, 0x24, 1, 2, 0x20, 0, 2, cpu_sel, 0, 0, 0),
+ /* TODO: 0x54 bit 14 and 0x54 bit 6 are supposed to be card detection clocks. */
+ ZX_CLK(SD0, 0x58, 1, 0x54, 12, 13, 0x50, 4, 3, sd0_sel, 0, 0, 0),
+ ZX_CLK(SD1, 0x58, 0, 0x54, 4, 5, 0x50, 8, 3, sd1_sel, 0, 0, 0),
+ /* This is some "denali" NAND, not the qspi connected one. */
+ ZX_CLK(NAND, 0x58, 4, 0x54, 20, 21, 0x50, 12, 2, nand_sel, 0, 0, 0),
+ ZX_CLK(SSC, 0x94, 24, 0x84, 1, 2, 0, 0, 0, clk_unknown, 0, 0, 0),
+ ZX_CLK(EDCP, 0x68, 0, 0x64, 2, 1, 0x50, 16, 2, edcp_sel, 0, 0, 0),
+ /* PDCFG. Like PMM, either clock bit will allow the device to function. */
+ ZX_CLK_CRIT(PDCFG, 0x94, 20, 0x88, 0, 1, 0x50, 16, 2, clk_unknown, 0, 0, 0),
+ /* There are a lot more VOU related controls in these registers, but turning off the main
+ * clock seems to shut off the entire VOU MMIO range.
+ */
+ ZX_CLK(VOU, 0x16c, 0, 0x168, 0, 1, 0, 0, 0, clk_main, 0, 0, 0),
+};
+
+static const struct zx297520v3_gate matrix_gates[] = {
+ /* ZTE's driver has a statemt to the effect of *(matrix->base+0x11C) = 5, with a comment
+ * suggesting that this sets a 50 mhz clock. The clock code itself lists the parents of
+ * these clock as 50mhz pll output, but the GMAC driver never enables the clocks.
+ *
+ * The clocks below are enabled by the boot loader though, so they are on. And it turns
+ * out that they are necessary for proper operation of the ethernet hardware. As far as
+ * I can see trough experimentation, bit 1 affects the PHY whereas 0 and 2 affect the
+ * MAC chip itself.
+ *
+ * Chain the wclk and rmii clk together for now. I haven't found a way to make either
+ * the mdio node or the phy node enable a clock. According to ethernet-phy.yaml it is
+ * supposed to be possible, but I can't find code to that effect in of_mdio.c.
+ */
+ {ZX297520V3_GMAC_PCLK, "gmac_pclk", "gpll_d4", 0x110, 0},
+ {ZX297520V3_GMAC_RMII, "gmac_rmii", "gpll_d4", 0x110, 1},
+ {ZX297520V3_GMAC_RMII, "gmac_wclk", "gmac_rmii", 0x110, 2},
+
+ /* ZSP aka LTE DSP clock. I think there is a mux at matrix+0x30, but I have no idea
+ * about the frequencies it selects. Gate is at matrix+0x3c.
+ */
+ {ZX297520V3_ZSP_WCLK, "zsp_wclk", "osc26m", 0x3c, 0},
+
+ /* Mailbox. I haven't found a reset for this. It seems to have a PCLK only - turning it off
+ * makes the MMIO area read 0x0. It looks like it does not need a WCLK. It generates IRQs
+ * fine with just bit 2 set. Bits 1 and 3 are 0 by default in this register.
+ */
+ {ZX297520V3_MBOX_PCLK, "mbox_pclk", "osc26m", 0x88, 2},
+
+ /* DMA Controller. It has a reset and PCLK, but no WCLK. */
+ {ZX297520V3_DMA_PCLK, "dma_pclk", "osc26m", 0x94, 3},
+
+ /* There is another clock controlling some "GSM" IP at 0xF3000000 in 0x88, bit 8. It appears
+ * to be a PCLK, but I have not found a matching WCLK or reset yet.
+ */
+
+ /* LSP uplink clocks. The PCLK is fairly obvious (disabling it shuts off the entire LSP
+ * register area). The WCLK speeds were deduced by setting timers and qspi muxes to a
+ * specific speed and seeing which bit in matrix+0x7c needs to be enabled for the device
+ * to work.
+ *
+ * Due to the timers I am certain about the 26mhz and 32khz clocks. I cannot directly
+ * observe the qspi mux frequency, so the clock rates depend on ZTE's qspi mux selection
+ * being correct.
+ *
+ * Two additional bits are specific to sound components - the mux for the LSP's TDM IP is
+ * in matrixclk and gets passed down. I2S has a mux in LSP, which can select the dpll_d4
+ * clock.
+ *
+ * This code is commented out until the next patch because disabling unused clocks without
+ * an LSP consumer breaks the UART.
+ */
+#if 0
+ {ZX297520V3_LSP_MPLL_D5_WCLK, "lsp_mpll_d5", "mpll_d5", 0x7c, 0},
+ {ZX297520V3_LSP_MPLL_D4_WCLK, "lsp_mpll_d4", "mpll_d4", 0x7c, 1},
+ {ZX297520V3_LSP_MPLL_D6_WCLK, "lsp_mpll_d6", "mpll_d6", 0x7c, 2},
+ {ZX297520V3_LSP_MPLL_D8_WCLK, "lsp_mpll_d8", "mpll_d8", 0x7c, 3},
+ {ZX297520V3_LSP_MPLL_D12_WCLK, "lsp_mpll_d12", "mpll_d12", 0x7c, 4},
+ {ZX297520V3_LSP_OSC26M_WCLK, "lsp_osc26m", "osc26m", 0x7c, 5},
+ {ZX297520V3_LSP_OSC32K_WCLK, "lsp_osc32k", "osc32k", 0x7c, 6},
+ {ZX297520V3_LSP_PCLK, "lsp_pclk", "osc26m", 0x7c, 7},
+ {ZX297520V3_LSP_TDM_WCLK, "lsp_tdm_wclk", "tdm_mux", 0x7c, 8},
+ {ZX297520V3_LSP_DPLL_D4_WCLK, "lsp_dpll_d4", "dpll_d4", 0x7c, 9},
+#endif
+};
+
+static int zx297520_matrixclk_probe(struct platform_device *pdev)
+{
+ struct zx29_clk_controller *matrix;
+ struct device *dev = &pdev->dev;
+ struct clk_hw *hw;
+ unsigned int i;
+ int res;
+
+ dev_info(dev, "Registering zx297520v3 matrix clocks\n");
+
+ matrix = devm_kzalloc(dev, offsetof(struct zx29_clk_controller,
+ resets[ZX297520V3_MATRIXRST_END]), GFP_KERNEL);
+ if (!matrix)
+ return -ENOMEM;
+
+ matrix->clocks = devm_kzalloc(dev, struct_size(matrix->clocks, hws,
+ ZX297520V3_MATRIXCLK_END), GFP_KERNEL);
+ if (!matrix->clocks)
+ return -ENOMEM;
+ matrix->clocks->num = ZX297520V3_MATRIXCLK_END;
+
+ matrix->base = devm_platform_ioremap_resource(pdev, 0);
+ WARN_ON(!matrix->base);
+
+ /* One stray mux: The TDM mux is in matrixclk and it is passed to the LSP controller. In a
+ * way the link gate (LSP_TDM_WCLK) could be considered a matching gate, but there is no
+ * reset and no pclk.
+ */
+ hw = devm_clk_hw_register_mux(dev, "tdm_mux", tdm_sel, ARRAY_SIZE(tdm_sel), 0,
+ matrix->base + 0x50, 24, 2, 0, ®_lock);
+
+ res = zx297520v3_composite(dev, matrix->base, matrix->clocks, matrix->resets,
+ matrix_clocks, ARRAY_SIZE(matrix_clocks));
+ if (res)
+ return res;
+
+ res = zx297520v3_gate(dev, matrix->base, matrix->clocks,
+ matrix_gates, ARRAY_SIZE(matrix_gates));
+ if (res)
+ return res;
+
+ for (i = 0; i < ZX297520V3_MATRIXCLK_END; i++) {
+ if (IS_ERR(matrix->clocks->hws[i])) {
+ pr_err("zx297520 clk %d: register failed with %ld\n",
+ i, PTR_ERR(matrix->clocks->hws[i]));
+ return -ENODEV;
+ }
+ }
+
+ res = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, matrix->clocks);
+ if (res)
+ return res;
+
+ matrix->resets[ZX297520V3_DMA_RESET].reg = matrix->base + 0x70;
+ matrix->resets[ZX297520V3_DMA_RESET].mask = BIT(0) | BIT(1);
+ matrix->resets[ZX297520V3_GMAC_RESET].reg = matrix->base + 0x114;
+ matrix->resets[ZX297520V3_GMAC_RESET].mask = BIT(0) | BIT(1);
+
+ matrix->rcdev.owner = THIS_MODULE;
+ matrix->rcdev.nr_resets = ZX297520V3_MATRIXRST_END;
+ matrix->rcdev.ops = &zx297520v3_rst_ops;
+ matrix->rcdev.of_node = dev->of_node;
+ return devm_reset_controller_register(dev, &matrix->rcdev);
+}
+
+static const struct of_device_id of_match_zx297520v3_matrixclk[] = {
+ { .compatible = "zte,zx297520v3-matrixclk"},
+ { }
+};
+MODULE_DEVICE_TABLE(of, of_match_zx297520v3_matrixclk);
+
+static struct platform_driver clk_zx297520v3_matrixclk = {
+ .probe = zx297520_matrixclk_probe,
+ .driver = {
+ .name = "clk-zx297520v3-matrixclk",
+ .of_match_table = of_match_zx297520v3_matrixclk,
+ },
+};
+module_platform_driver(clk_zx297520v3_matrixclk);
+
MODULE_AUTHOR("Stefan Dösinger <stefandoesinger@gmail.com>");
MODULE_DESCRIPTION("ZTE zx297520v3 clock driver");
MODULE_LICENSE("GPL");
--
2.53.0
next prev parent reply other threads:[~2026-05-10 21:50 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-10 21:49 [PATCH RFC v2 0/4] ZTE zx297520v3 clock bindings and driver Stefan Dösinger
2026-05-10 21:49 ` [PATCH RFC v2 1/4] dt-bindings: clk: zte: Add zx297520v3 clock and reset bindings Stefan Dösinger
2026-05-10 21:49 ` [PATCH RFC v2 2/4] clk: zte: Introduce a driver for zx297520v3 top clocks and resets Stefan Dösinger
2026-05-10 21:49 ` Stefan Dösinger [this message]
2026-05-10 21:49 ` [PATCH RFC v2 4/4] clk: zte: Introduce a driver for zx297520v3 LSP " Stefan Dösinger
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260511-zx29clk-v2-3-29f0edc300f5@gmail.com \
--to=stefandoesinger@gmail.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-clk@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mturquette@baylibre.com \
--cc=p.zabel@pengutronix.de \
--cc=robh@kernel.org \
--cc=sboyd@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox