From: Jie Gan <jie.gan@oss.qualcomm.com>
To: Qiang Yu <qiang.yu@oss.qualcomm.com>,
Bjorn Andersson <andersson@kernel.org>,
Michael Turquette <mturquette@baylibre.com>,
Stephen Boyd <sboyd@kernel.org>,
Brian Masney <bmasney@redhat.com>, Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Taniya Das <taniya.das@oss.qualcomm.com>,
Konrad Dybcio <konradybcio@kernel.org>
Cc: linux-arm-msm@vger.kernel.org, linux-clk@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
krishna.chundru@oss.qualcomm.com
Subject: Re: [PATCH v4 2/7] clk: qcom: Add generic clkref_en support
Date: Thu, 28 May 2026 11:03:45 +0800 [thread overview]
Message-ID: <332c5b1e-8b70-4902-99f3-536dfe8e32bf@oss.qualcomm.com> (raw)
In-Reply-To: <20260527-tcsr_qref_0527-v4-2-ded83866c9d9@oss.qualcomm.com>
On 5/28/2026 10:29 AM, Qiang Yu wrote:
> 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 | 69 +++++++++++++++
> 3 files changed, 275 insertions(+)
>
> diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
> index e100cfd6a52d..c5b02360861d 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..213c0f58bb36
> --- /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_update_bits(rclk->regmap, rclk->desc.offset, QCOM_CLK_REF_EN_MASK,
> + QCOM_CLK_REF_EN_MASK);
> + if (ret)
> + return ret;
> +
> + udelay(10);
try usleep_range instead of udelay for better power consumption.
> +
> + return 0;
> +}
> +
> +static void qcom_clk_ref_disable(struct clk_hw *hw)
> +{
> + struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw);
> +
> + regmap_update_bits(rclk->regmap, rclk->desc.offset, QCOM_CLK_REF_EN_MASK, 0);
> + 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 ret;
regmap_read returns a negative error code on failure, but the
clk_ops.is_enabled() treats the non-zero value as enabled.
Thanks,
Jie
> +
> + 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 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);
> + }
> +
> + clk_ref->init_data.name = clk_ref->desc.name;
> + clk_ref->init_data.parent_data = &qcom_clk_ref_parent_data;
> + clk_ref->init_data.num_parents = 1;
> + clk_ref->init_data.ops = &qcom_clk_ref_ops;
> + clk_ref->hw.init = &clk_ref->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..09e2e3178cfb
> --- /dev/null
> +++ b/include/linux/clk/qcom.h
> @@ -0,0 +1,69 @@
> +/* 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
> + * @init_data: common clock framework registration data
> + * @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 clk_init_data init_data;
> + 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
>
next prev parent reply other threads:[~2026-05-28 3:03 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-28 2:29 [PATCH v4 0/7] clk: qcom: Add common clkref support and migrate Glymur and Mahua Qiang Yu
2026-05-28 2:29 ` [PATCH v4 1/7] dt-bindings: clock: qcom,sm8550-tcsr: Add QREF/REFGEN supply properties for glymur and mahua Qiang Yu
2026-05-28 7:57 ` Krzysztof Kozlowski
2026-05-28 12:28 ` Qiang Yu
2026-05-28 12:34 ` Krzysztof Kozlowski
2026-05-29 7:05 ` Qiang Yu
2026-05-28 2:29 ` [PATCH v4 2/7] clk: qcom: Add generic clkref_en support Qiang Yu
2026-05-28 3:03 ` Jie Gan [this message]
2026-05-28 13:06 ` Qiang Yu
2026-05-28 13:46 ` Jie Gan
2026-05-28 15:01 ` Dmitry Baryshkov
2026-05-29 6:43 ` Qiang Yu
2026-05-29 6:45 ` Qiang Yu
2026-06-09 12:38 ` Konrad Dybcio
2026-06-15 8:34 ` Qiang Yu
2026-06-09 12:57 ` Konrad Dybcio
2026-06-15 8:40 ` Qiang Yu
2026-05-28 2:29 ` [PATCH v4 3/7] clk: qcom: tcsrcc-glymur: Migrate tcsr_pcie_N_clkref_en to clk_ref common helper Qiang Yu
2026-06-09 13:02 ` Konrad Dybcio
2026-06-15 8:46 ` Qiang Yu
2026-05-28 2:29 ` [PATCH v4 4/7] clk: qcom: tcsrcc-glymur: Add Mahua QREF regulator support Qiang Yu
2026-05-28 2:29 ` [PATCH v4 5/7] arm64: dts: qcom: glymur: Add QREF regulator supplies to TCSR Qiang Yu
2026-05-28 2:29 ` [PATCH v4 6/7] arm64: dts: qcom: mahua: " Qiang Yu
2026-05-28 2:29 ` [PATCH v4 7/7] arm64: dts: qcom: mahua: Switch pcie5_phy ref clock to RPMH_CXO_CLK Qiang Yu
2026-06-09 13:06 ` Konrad Dybcio
2026-06-09 13:55 ` Krzysztof Kozlowski
2026-06-09 14:07 ` Konrad Dybcio
2026-06-15 8:51 ` Qiang Yu
2026-06-17 12:11 ` Konrad Dybcio
2026-06-18 2:34 ` Qiang Yu
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=332c5b1e-8b70-4902-99f3-536dfe8e32bf@oss.qualcomm.com \
--to=jie.gan@oss.qualcomm.com \
--cc=andersson@kernel.org \
--cc=bmasney@redhat.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=konradybcio@kernel.org \
--cc=krishna.chundru@oss.qualcomm.com \
--cc=krzk+dt@kernel.org \
--cc=linux-arm-msm@vger.kernel.org \
--cc=linux-clk@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mturquette@baylibre.com \
--cc=qiang.yu@oss.qualcomm.com \
--cc=robh@kernel.org \
--cc=sboyd@kernel.org \
--cc=taniya.das@oss.qualcomm.com \
/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®