Re: [PATCH 2/5] clk: qcom: Add a driver for PDM GP_MN fractional clock divider

From: Dmitry Baryshkov

Date: Sun Jun 07 2026 - 23:32:33 EST


On Tue, Jun 02, 2026 at 08:51:50PM +0530, Taniya Das wrote:
> The PDM (Pulse Density Modulation) hardware block on Qualcomm SoCs
> contains a GP_MN clock divider that produces a fractional output
> frequency from a fixed input clock (typically TCXO4):
>
> Fout = Fin * (M / N)
>
> The hardware encodes the period in the NDIV register as the 1's
> complement of (N - M), and controls the duty cycle via a separate
> DUTY register that counts the number of low-phase native clock
> cycles over the period N.
>
> Add a standalone platform driver for this block that uses
> rational_best_approximation() to find the closest M/N pair within
> the 9-bit M and 13-bit N hardware limits, programs the MDIV, NDIV,
> and DUTY registers via regmap, and implements the full clk_ops
> surface including determine_rate, set_rate, recalc_rate,
> get_duty_cycle, and set_duty_cycle. The PDM AHB bus clock is gated
> around every register access.
>
> Signed-off-by: Taniya Das <taniya.das@xxxxxxxxxxxxxxxx>
> ---
> drivers/clk/qcom/Kconfig | 15 ++
> drivers/clk/qcom/Makefile | 1 +
> drivers/clk/qcom/clk-gp-mnd.c | 333 ++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 349 insertions(+)
>
> diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig
> index d9cff5b0281d8cc373b8ab14683370cb9b7f8bf3..df27aa10243435a20a57cca3ed4644284630d11e 100644
> --- a/drivers/clk/qcom/Kconfig
> +++ b/drivers/clk/qcom/Kconfig
> @@ -1759,4 +1759,19 @@ config SM_VIDEOCC_8450
> SM8450 or SM8475 devices.
> Say Y if you want to support video devices and functionality such as
> video encode/decode.
> +
> +config QCOM_CLK_GP_MND
> + tristate "Qualcomm PDM GP_MN clock divider"
> + depends on ARM64 || COMPILE_TEST
> + help
> + Support for the Qualcomm PDM GP_MN clock divider found in PDM
> + (Pulse Density Modulation) hardware blocks.
> + Given an input clock of frequency Fin (TCXO4), the output
> + frequency is Fout = Fin * (M / N). For every N input cycles
> + the divider produces M output cycles. D controls the duty
> + cycle: it is the number of native clock cycles in which the
> + GP_MN output is low, counted over 8192 native clock cycles.
> +
> + Say Y or M if you want to support GP_MN-based frequency and
> + duty-cycle configuration on Qualcomm SoCs.
> endif
> diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
> index e100cfd6a52de9f88f11720d9c2043db5e553618..438f59b25c009ee72308fe41707d6efff6613690 100644
> --- a/drivers/clk/qcom/Makefile
> +++ b/drivers/clk/qcom/Makefile
> @@ -207,6 +207,7 @@ obj-$(CONFIG_SM_VIDEOCC_8550) += videocc-sm8550.o
> obj-$(CONFIG_SM_VIDEOCC_8750) += videocc-sm8750.o
> obj-$(CONFIG_SM_VIDEOCC_MILOS) += videocc-milos.o
> obj-$(CONFIG_SPMI_PMIC_CLKDIV) += clk-spmi-pmic-div.o
> +obj-$(CONFIG_QCOM_CLK_GP_MND) += clk-gp-mnd.o
> obj-$(CONFIG_KPSS_XCC) += kpss-xcc.o
> obj-$(CONFIG_QCOM_HFPLL) += hfpll.o
> obj-$(CONFIG_KRAITCC) += krait-cc.o
> diff --git a/drivers/clk/qcom/clk-gp-mnd.c b/drivers/clk/qcom/clk-gp-mnd.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..826b6b62ddc7b272511accde1ca0e885018a8064
> --- /dev/null
> +++ b/drivers/clk/qcom/clk-gp-mnd.c
> @@ -0,0 +1,333 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/pinctrl/consumer.h>
> +#include <linux/platform_device.h>
> +#include <linux/rational.h>
> +#include <linux/regmap.h>
> +
> +/*
> + * PDM GP_MND clock divider register offsets.
> + *
> + * The hardware computes:
> + * Fout = Fin * (M / N)
> + *
> + * with duty cycle controlled by D, where M < D < (N - M).
> + *
> + * Register encoding:
> + * MDIV = M
> + * NDIV = ~(N - M) [1's complement of (N - M), masked to N_REG_WIDTH bits]
> + * DUTY = D
> + */
> +#define GP_MND_MDIV_REG 0x0
> +#define GP_MND_NDIV_REG 0x4
> +#define GP_MND_DUTY_REG 0x8
> +
> +#define GP_MND_M_WIDTH 9
> +#define GP_MND_N_WIDTH 13
> +
> +#define GP_MND_MAX_M GENMASK(GP_MND_M_WIDTH - 1, 0)
> +#define GP_MND_MAX_N GENMASK(GP_MND_N_WIDTH - 1, 0)
> +
> +/**
> + * struct clk_gp_mnd - GP_MND fractional clock divider
> + * @pdm_ahb_clk: AHB bus clock required for register access
> + * @regmap: register map for the PDM block
> + * @hw: handle between common and hardware-specific interfaces
> + * @m_val: M value (numerator)
> + * @n_val: N value (period)
> + */
> +struct clk_gp_mnd {
> + struct clk *pdm_ahb_clk;
> + struct regmap *regmap;
> + struct clk_hw hw;
> + unsigned int m_val;
> + unsigned int n_val;
> +};
> +
> +#define to_clk_gp_mnd(_hw) container_of(_hw, struct clk_gp_mnd, hw)
> +
> +static int gp_mnd_clk_determine_rate(struct clk_hw *hw,
> + struct clk_rate_request *req)
> +{
> + unsigned long m = 0, n = 0;
> +
> + rational_best_approximation(req->rate, req->best_parent_rate,
> + (unsigned long)GP_MND_MAX_M,
> + (unsigned long)GP_MND_MAX_N,
> + &m, &n);
> +
> + if (!m || !n)
> + return -EINVAL;
> +
> + /* N = 2M + 1 leaves no valid D satisfying M < D < (N - M) */
> + if (n == 2 * m + 1)
> + return -EINVAL;
> +
> + req->rate = DIV_ROUND_CLOSEST_ULL((u64)req->best_parent_rate * m, n);
> +
> + return 0;
> +}
> +
> +static int gp_mnd_clk_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + struct clk_gp_mnd *gp = to_clk_gp_mnd(hw);
> + unsigned long m = 0, n = 0;
> + unsigned int d_val, n_val;
> + int ret;
> +
> + rational_best_approximation(rate, parent_rate,
> + (unsigned long)GP_MND_MAX_M,
> + (unsigned long)GP_MND_MAX_N,
> + &m, &n);
> +
> + if (!m || !n)
> + return -EINVAL;
> +
> + /*
> + * When N = 2M + 1 the valid D range [M+1, M] is empty; no duty
> + * cycle can satisfy M < D < (N - M). Reject before touching hw.
> + */
> + if (n == 2 * m + 1)
> + return -EINVAL;
> +
> + ret = clk_prepare_enable(gp->pdm_ahb_clk);

Can we use CLK_OPS_PARENT_ENABLE or pm_clk instead? Having to manually
toggle the clock looks like a coomplete overkill.

> + if (ret)
> + return ret;
> +
> +
> + ret = of_property_read_string_index(dev->of_node,
> + "clock-output-names", 0,
> + &init.name);

Do we need it? Can we generate the name instead?

> + if (ret)
> + return dev_err_probe(dev, ret, "missing clock-output-names\n");
> +
> + gp->hw.init = &init;
> +
> + pin = devm_pinctrl_get(dev);
> + if (IS_ERR(pin))
> + return dev_err_probe(dev, PTR_ERR(pin), "missing pinctrl device\n");
> +
> + pin_default_state = pinctrl_lookup_state(pin, "active");
> + if (IS_ERR(pin_default_state))
> + return dev_err_probe(dev, PTR_ERR(pin_default_state),
> + "missing pinctrl default state\n");

Isn't it done by default for you?

> +
> + ret = pinctrl_select_state(pin, pin_default_state);
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "failed to select pinctrl default state\n");
> +
> + ret = devm_clk_hw_register(dev, &gp->hw);
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "failed to register gp_mnd clock\n");
> +
> + return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &gp->hw);
> +}
> +
> +static const struct of_device_id clk_gp_mnd_match_table[] = {
> + { .compatible = "qcom,clk-gp-mnd" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, clk_gp_mnd_match_table);
> +
> +static struct platform_driver clk_gp_mnd_driver = {
> + .probe = clk_gp_mnd_probe,
> + .driver = {
> + .name = "qcom-clk-gp-mnd",
> + .of_match_table = clk_gp_mnd_match_table,
> + },
> +};
> +module_platform_driver(clk_gp_mnd_driver);
> +
> +MODULE_DESCRIPTION("Qualcomm PDM GP_MND clock divider driver");
> +MODULE_LICENSE("GPL");
>
> --
> 2.34.1
>

--
With best wishes
Dmitry