From: Alex Elder <elder@riscstar.com>
To: sboyd@kernel.org, bmasney+clk@redhat.com,
jbrunet+clk@baylibre.com, robh@kernel.org, krzk+dt@kernel.org,
conor+dt@kernel.org, lee@kernel.org, andersson@kernel.org,
konradybcio@kernel.org, abelvesa@kernel.org, kees@kernel.org,
gustavoars@kernel.org, p.zabel@pengutronix.de
Cc: daniel@riscstar.com, mohd.anwar@oss.qualcomm.com,
lorenzo.bianconi@oss.qualcomm.com, linux-clk@vger.kernel.org,
devicetree@vger.kernel.org, mfd@lists.linux.dev,
linux-arm-msm@vger.kernel.org, linux-hardening@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: [PATCH 3/4] clk: toshiba: introduce a TC9564 SoC clock and reset driver
Date: Fri, 18 Sep 2026 11:52:32 -0500 [thread overview]
Message-ID: <20260918165234.687224-4-elder@riscstar.com> (raw)
In-Reply-To: <20260918165234.687224-1-elder@riscstar.com>
Define a new platform driver that manages clock and reset signals
within the TC9564 SoC. There are 21 clocks, which can only be
enabled and disabled, as well as 13 reset signals.
Two registers manage the state of the clocks and two others manage
the state of the resets. The registers are accessed via a regmap
supplied by a system controller, which coordinates access to a
region of memory that will be shared with another driver.
Access to the memory region is provided via a BAR on a PCIe endpoint
function embedded in the TC9564 SoC. For that reason, neither the
PCIe clock nor PCIe reset can be manipulated by this driver (they
are assumed always on and deasserted, respectively).
Similarly, control is not available for the I2C clock and reset,
because the PCIe subsystem on the TC9564 relies on I2C
Co-developed-by: Daniel Thompson <daniel@riscstar.com>
Signed-off-by: Daniel Thompson <daniel@riscstar.com>
Signed-off-by: Alex Elder <elder@riscstar.com>
---
MAINTAINERS | 1 +
drivers/clk/Kconfig | 11 ++
drivers/clk/Makefile | 1 +
drivers/clk/clk-tc9564.c | 366 +++++++++++++++++++++++++++++++++++++++
4 files changed, 379 insertions(+)
create mode 100644 drivers/clk/clk-tc9564.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 66d0e7e65adcb..39346a5cd9a7f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27675,6 +27675,7 @@ M: Alex Elder <elder@kernel.org>
M: Daniel Thompson <danielt@kernel.org>
S: Maintained
F: Documentation/devicetree/bindings/clock/toshiba,tc9564-clock.yaml
+F: drivers/clk/clk-tc9564.c
F: include/dt-bindings/clock/toshiba,tc9564.h
TOSHIBA TC9564 PCI DRIVER
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index f9592fd9ec2bb..50efa10d48450 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -292,6 +292,17 @@ config COMMON_CLK_S2MPS11
clock. These multi-function devices have two (S2MPS14) or three
(S2MPS11, S5M8767) fixed-rate oscillators, clocked at 32KHz each.
+config COMMON_CLK_TC9564
+ tristate "Toshiba TC9564 clock support"
+ depends on TC9564_PCI
+ default m
+ help
+ This enables support for the clock and reset controller embedded
+ in the Toshiba TC9564 (and Qualcomm QPS615) SoC. The state of
+ clock and reset lines is controlled by MMIO to a region managed
+ by a system controller; this ensures access to the region is
+ coordinated between this and other drivers.
+
config CLK_TWL
tristate "Clock driver for the TWL PMIC family"
depends on TWL4030_CORE
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index b18af485d7f03..a98ea8ff92066 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -110,6 +110,7 @@ obj-$(CONFIG_COMMON_CLK_SI570) += clk-si570.o
obj-$(CONFIG_COMMON_CLK_SP7021) += clk-sp7021.o
obj-$(CONFIG_COMMON_CLK_STM32F) += clk-stm32f4.o
obj-$(CONFIG_COMMON_CLK_STM32H7) += clk-stm32h7.o
+obj-$(CONFIG_COMMON_CLK_TC9564) += clk-tc9564.o
obj-$(CONFIG_COMMON_CLK_TPS68470) += clk-tps68470.o
obj-$(CONFIG_CLK_TWL6040) += clk-twl6040.o
obj-$(CONFIG_CLK_TWL) += clk-twl.o
diff --git a/drivers/clk/clk-tc9564.c b/drivers/clk/clk-tc9564.c
new file mode 100644
index 0000000000000..4f900bc8994a7
--- /dev/null
+++ b/drivers/clk/clk-tc9564.c
@@ -0,0 +1,366 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright (C) 2026 by RISCstar Solutions Corporation. All rights reserved.
+ */
+
+#include <linux/bits.h>
+#include <linux/clk-provider.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset-controller.h>
+
+#include <dt-bindings/clock/toshiba,tc9564.h>
+
+#define CLK_CTRL0_OFFSET 0x1004
+#define RST_CTRL0_OFFSET 0x1008
+#define CLK_CTRL1_OFFSET 0x100c
+#define RST_CTRL1_OFFSET 0x1010
+
+struct tc9564_clock_init {
+ const char *name; /* NULL means unused entry */
+ u32 offset;
+ u32 mask;
+};
+
+struct tc9564_clock {
+ struct clk_hw hw;
+ u32 which;
+ u32 offset; /* CLK_CTRL0_OFFSET or CLK_CTRL1_OFFSET */
+ u32 mask; /* Zero means undefined clock */
+};
+
+struct tc9564_reset {
+ u32 offset; /* RST_CTRL0_OFFSET or RST_CTRL1_OFFSET */
+ u32 mask; /* Zero means undefined reset */
+};
+
+struct tc9564_clocks {
+ struct device *dev;
+ struct regmap *regmap;
+ struct reset_controller_dev rcdev;
+ size_t clock_count;
+ struct tc9564_clock clocks[] __counted_by(clock_count);
+};
+
+#define TC9564_CLOCK_INIT0(_name, _bit) __TC9564_CLOCK_INIT(_name, 0, _bit)
+#define TC9564_CLOCK_INIT1(_name, _bit) __TC9564_CLOCK_INIT(_name, 1, _bit)
+
+#define __TC9564_CLOCK_INIT(_name, _reg, _bit) \
+ [CLOCK_##_name] = { \
+ .name = #_name, \
+ .offset = CLK_CTRL ## _reg ## _OFFSET, \
+ .mask = BIT(_bit), \
+ }
+
+#define TC9564_RESET_INIT0(_name, _bit) __TC9564_RESET_INIT(_name, 0, _bit)
+#define TC9564_RESET_INIT1(_name, _bit) __TC9564_RESET_INIT(_name, 1, _bit)
+
+#define __TC9564_RESET_INIT(_name, _reg, _bit) \
+ [RESET_##_name] = { \
+ .offset = RST_CTRL ## _reg ## _OFFSET, \
+ .mask = BIT(_bit), \
+ }
+
+static const struct tc9564_clock_init tc9564_clock_init[] = {
+ TC9564_CLOCK_INIT0(MCU, 0),
+ TC9564_CLOCK_INIT0(INTC, 4),
+ /* TC9564_CLOCK_INIT0(PCIE, 9), */
+ /* TC9564_CLOCK_INIT0(I2C, 12), */
+ TC9564_CLOCK_INIT0(SRAM, 13),
+ TC9564_CLOCK_INIT0(UART, 16),
+ TC9564_CLOCK_INIT0(MSIGEN, 18),
+ TC9564_CLOCK_INIT0(PLL, 24),
+ TC9564_CLOCK_INIT0(SGMII, 25),
+ TC9564_CLOCK_INIT0(REFCLKO, 26),
+
+ TC9564_CLOCK_INIT0(MAC0_TX, 7),
+ TC9564_CLOCK_INIT0(MAC0_RX, 14),
+ TC9564_CLOCK_INIT0(MAC0_125M, 29),
+ TC9564_CLOCK_INIT0(MAC0_312_5M, 30),
+ TC9564_CLOCK_INIT0(MAC0_ALL, 31),
+
+ TC9564_CLOCK_INIT1(MAC1_TX, 7),
+ TC9564_CLOCK_INIT1(MAC1_RX, 14),
+ TC9564_CLOCK_INIT1(MAC1_RMII, 15),
+ TC9564_CLOCK_INIT1(MAC1_125M, 29),
+ TC9564_CLOCK_INIT1(MAC1_312_5M, 30),
+ TC9564_CLOCK_INIT1(MAC1_ALL, 31),
+};
+#define TC9564_CLOCK_COUNT ARRAY_SIZE(tc9564_clock_init)
+
+static const struct tc9564_reset tc9564_reset[] = {
+ TC9564_RESET_INIT0(MCU, 0),
+ TC9564_RESET_INIT0(MCU1, 1),
+ TC9564_RESET_INIT0(INTC, 4),
+ /* TC9564_RESET_INIT0(PCIE, 9), */
+ /* TC9564_RESET_INIT0(I2C, 12), */
+ TC9564_RESET_INIT0(UART, 16),
+ TC9564_RESET_INIT0(MSIGEN, 18),
+
+ TC9564_RESET_INIT0(MAC0_MAC, 7),
+ TC9564_RESET_INIT0(MAC0_PMA, 30),
+ TC9564_RESET_INIT0(MAC0_XPCS, 31),
+
+ TC9564_RESET_INIT1(MAC1_MAC, 7),
+ TC9564_RESET_INIT1(MAC1_PMA, 30),
+ TC9564_RESET_INIT1(MAC1_XPCS, 31),
+};
+#define TC9564_RESET_COUNT ARRAY_SIZE(tc9564_reset)
+
+static const struct tc9564_clock *hw_to_tc9564_clock(struct clk_hw *hw)
+{
+ return container_of_const(hw, struct tc9564_clock, hw);
+}
+
+static const struct tc9564_clocks *
+tc9564_clock_to_clocks(const struct tc9564_clock *clock)
+{
+ u32 which = clock->which;
+
+ if (which >= TC9564_CLOCK_COUNT)
+ return ERR_PTR(-ENXIO);
+
+ return container_of_const(clock, struct tc9564_clocks, clocks[which]);
+}
+
+static int tc9564_clk_manage(struct clk_hw *hw, bool enable)
+{
+ const struct tc9564_clock *clock = hw_to_tc9564_clock(hw);
+ const struct tc9564_clocks *clocks;
+ u32 offset = clock->offset;
+ u32 mask = clock->mask;
+
+ clocks = tc9564_clock_to_clocks(clock);
+ if (IS_ERR(clocks) || !mask) {
+ dev_err(clk_hw_get_dev(hw), "invalid clock (%s id %u)\n",
+ enable ? "enable" : "disable", clock->which);
+ return -ENXIO;
+ }
+
+ return regmap_update_bits(clocks->regmap, offset, mask,
+ enable ? mask : 0);
+}
+
+static int tc9564_clk_enable(struct clk_hw *hw)
+{
+ return tc9564_clk_manage(hw, true);
+}
+
+static void tc9564_clk_disable(struct clk_hw *hw)
+{
+ (void)tc9564_clk_manage(hw, false);
+}
+
+static const struct clk_ops tc9564_clk_ops = {
+ .enable = tc9564_clk_enable,
+ .disable = tc9564_clk_disable,
+};
+
+static void tc9564_clock_disable_all(struct tc9564_clocks *clocks)
+{
+ for (int i = 0; i < clocks->clock_count; i++) {
+ const struct tc9564_clock *clock = &clocks->clocks[i];
+
+ if (clock->mask)
+ regmap_update_bits(clocks->regmap, clock->offset,
+ clock->mask, 0);
+ }
+}
+
+static struct clk_hw *tc9564_clk_hw_get(struct of_phandle_args *clkspec,
+ void *data)
+{
+ struct tc9564_clocks *clocks = data;
+ unsigned int i = clkspec->args[0];
+
+ if (i < clocks->clock_count)
+ return &clocks->clocks[i].hw;
+
+ dev_err(clocks->dev, "invalid index %u\n", i);
+
+ return ERR_PTR(-EINVAL);
+}
+
+static struct tc9564_clocks *tc9564_clk_init(struct device *dev)
+{
+ struct tc9564_clocks *clocks;
+ size_t clocks_size;
+ int ret;
+
+ clocks_size = struct_size(clocks, clocks, TC9564_CLOCK_COUNT);
+ clocks = devm_kzalloc(dev, clocks_size, GFP_KERNEL);
+ if (!clocks)
+ return ERR_PTR(-ENOMEM);
+
+ clocks->dev = dev;
+ clocks->clock_count = TC9564_CLOCK_COUNT;
+
+ clocks->regmap = syscon_node_to_regmap(dev_of_node(dev->parent));
+ if (IS_ERR(clocks->regmap)) {
+ dev_err(dev, "failed to get config regmap\n");
+ return ERR_CAST(clocks->regmap);
+ }
+
+ for (u32 i = 0; i < TC9564_CLOCK_COUNT; i++) {
+ const struct tc9564_clock_init *clock_init;
+ struct clk_init_data init = { };
+ struct tc9564_clock *clock;
+
+ clock_init = &tc9564_clock_init[i];
+ if (!clock_init->name)
+ continue;
+
+ init.name = clock_init->name;
+ init.ops = &tc9564_clk_ops;
+
+ clock = &clocks->clocks[i];
+ clock->hw.init = &init;
+
+ ret = devm_clk_hw_register(dev, &clock->hw);
+ if (ret) {
+ dev_err(dev, "failed to register clock \"%s\"\n",
+ init.name);
+ return ERR_PTR(ret);
+ }
+
+ clock->which = i;
+ clock->offset = clock_init->offset;
+ clock->mask = clock_init->mask;
+ }
+
+ ret = devm_of_clk_add_hw_provider(dev, tc9564_clk_hw_get, clocks);
+ if (ret) {
+ dev_err(dev, "failed to add clock hardware provider\n");
+ return ERR_PTR(ret);
+ }
+
+ return clocks;
+}
+
+static const struct tc9564_clocks *
+rcdev_to_tc9564_clocks(struct reset_controller_dev *rcdev)
+{
+ return container_of_const(rcdev, struct tc9564_clocks, rcdev);
+}
+
+static int tc9564_reset_manage(struct reset_controller_dev *rcdev,
+ unsigned long id, bool assert)
+{
+ const struct tc9564_clocks *clocks = rcdev_to_tc9564_clocks(rcdev);
+
+ if (id < rcdev->nr_resets) {
+ const struct tc9564_reset *reset = &tc9564_reset[id];
+ u32 mask = reset->mask;
+
+ if (mask)
+ return regmap_update_bits(clocks->regmap,
+ reset->offset, mask,
+ assert ? mask : 0);
+ }
+
+ dev_err(clocks->dev, "invalid reset (%sassert id %lu)\n",
+ assert ? "" : "de", id);
+
+ return -ENXIO;
+}
+
+static int tc9564_reset_assert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ return tc9564_reset_manage(rcdev, id, true);
+}
+
+static int tc9564_reset_deassert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ return tc9564_reset_manage(rcdev, id, false);
+}
+
+static const struct reset_control_ops tc9564_reset_control_ops = {
+ .assert = tc9564_reset_assert,
+ .deassert = tc9564_reset_deassert,
+};
+
+static void tc9564_reset_assert_all(struct tc9564_clocks *clocks)
+{
+ for (u32 id = 0; id < TC9564_RESET_COUNT; id++)
+ if (tc9564_reset[id].mask)
+ tc9564_reset_manage(&clocks->rcdev, id, true);
+}
+
+static int tc9564_reset_init(struct tc9564_clocks *clocks)
+{
+ struct reset_controller_dev *rcdev = &clocks->rcdev;
+
+ rcdev->ops = &tc9564_reset_control_ops;
+ rcdev->owner = THIS_MODULE;
+ rcdev->dev = clocks->dev;
+ rcdev->of_node = dev_of_node(clocks->dev);
+ rcdev->nr_resets = TC9564_RESET_COUNT;
+
+ return devm_reset_controller_register(clocks->dev, rcdev);
+}
+
+static int tc9564_clk_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct tc9564_clocks *clocks;
+ int ret;
+
+ if (!dev_of_node(dev))
+ return dev_err_probe(dev, -EINVAL, "no devicetree node\n");
+
+ clocks = tc9564_clk_init(dev);
+ if (IS_ERR(clocks))
+ return dev_err_probe(dev, PTR_ERR(clocks),
+ "failed to initialize clocks\n");
+
+ ret = tc9564_reset_init(clocks);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to initialize resets\n");
+
+ /* Force all resets to be initially asserted */
+ tc9564_reset_assert_all(clocks);
+
+ /* Force all clocks to be initially disabled */
+ tc9564_clock_disable_all(clocks);
+
+ platform_set_drvdata(pdev, clocks);
+
+ return 0;
+}
+
+static void tc9564_clk_remove(struct platform_device *pdev)
+{
+ struct tc9564_clocks *clocks = platform_get_drvdata(pdev);
+
+ /* Leave all resets to be deasserted when done */
+ tc9564_reset_assert_all(clocks);
+
+ /* Leave all clocks disabled when done */
+ tc9564_clock_disable_all(clocks);
+}
+
+static const struct of_device_id tc9564_clk_ids[] = {
+ { .compatible = "toshiba,tc9564-clock" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, tc9564_clk_ids);
+
+static struct platform_driver tc9564_clk_driver = {
+ .probe = tc9564_clk_probe,
+ .remove = tc9564_clk_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = tc9564_clk_ids,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+};
+module_platform_driver(tc9564_clk_driver);
+
+MODULE_DESCRIPTION("Toshiba TC9564 Clock and Reset Driver");
+MODULE_LICENSE("GPL");
--
2.53.0
next prev parent reply other threads:[~2026-09-18 16:52 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-18 16:52 [PATCH 0/4] clk: introduce TC9564 clock and reset Alex Elder
2026-09-18 16:52 ` [PATCH 1/4] dt-bindings: mfd: introduce the TC9564 config syscon Alex Elder
2026-09-18 16:52 ` [PATCH 2/4] dt-bindings: clock: introduce toshiba,tc9564-clock.yaml Alex Elder
2026-09-18 16:52 ` Alex Elder [this message]
2026-09-18 16:52 ` [PATCH 4/4] arm64: dts: qcom: qcs6490-rb3gen2: add the clock controller Alex Elder
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=20260918165234.687224-4-elder@riscstar.com \
--to=elder@riscstar.com \
--cc=abelvesa@kernel.org \
--cc=andersson@kernel.org \
--cc=bmasney+clk@redhat.com \
--cc=conor+dt@kernel.org \
--cc=daniel@riscstar.com \
--cc=devicetree@vger.kernel.org \
--cc=gustavoars@kernel.org \
--cc=jbrunet+clk@baylibre.com \
--cc=kees@kernel.org \
--cc=konradybcio@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=lee@kernel.org \
--cc=linux-arm-msm@vger.kernel.org \
--cc=linux-clk@vger.kernel.org \
--cc=linux-hardening@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=lorenzo.bianconi@oss.qualcomm.com \
--cc=mfd@lists.linux.dev \
--cc=mohd.anwar@oss.qualcomm.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
all inboxes | Powered by JetHome®