From: Brian Masney <bmasney@redhat.com>
To: Alex Elder <elder@riscstar.com>
Cc: 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,
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: Re: [PATCH 3/4] clk: toshiba: introduce a TC9564 SoC clock and reset driver
Date: Mon, 21 Sep 2026 18:59:43 -0400 [thread overview]
Message-ID: <arG238GS8bMB6DkW@redhat.com> (raw)
In-Reply-To: <20260918165234.687224-4-elder@riscstar.com>
Hi Alex,
On Fri, Sep 18, 2026 at 11:52:32AM -0500, Alex Elder wrote:
> 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
select RESET_CONTROLLER
> + default m
default n
> + 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.
Rather than 'other drivers', outline specifically which 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>
Uwe already pointed out removing this.
> +#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), */
A comment would be useful here to outline why these are commented out.
Is the PCIE one comment out because of what's outlined in the commit
message?
> + 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,
I'd rather not have the extra spaces because sometimes these get out of
line with new members that need more space. Also tc9564_clk_ops above
doesn't have the extra spaces.
> +};
> +
> +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);
The comment says deasserted but the code calls assert_all.
Brian
> +
> + /* 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-21 22:59 UTC|newest]
Thread overview: 15+ 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-20 18:18 ` Krzysztof Kozlowski
2026-09-21 21:46 ` Alex Elder
2026-09-18 16:52 ` [PATCH 2/4] dt-bindings: clock: introduce toshiba,tc9564-clock.yaml Alex Elder
2026-09-20 18:21 ` Krzysztof Kozlowski
2026-09-21 21:46 ` Alex Elder
2026-09-22 13:13 ` Alex Elder
2026-09-18 16:52 ` [PATCH 3/4] clk: toshiba: introduce a TC9564 SoC clock and reset driver Alex Elder
2026-09-20 19:39 ` Uwe Kleine-König
2026-09-22 12:37 ` Alex Elder
2026-09-22 12:53 ` Uwe Kleine-König
2026-09-21 22:59 ` Brian Masney [this message]
2026-09-22 13:33 ` Alex Elder
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=arG238GS8bMB6DkW@redhat.com \
--to=bmasney@redhat.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=elder@riscstar.com \
--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®