// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include #include #include #include #include #include #include #include #include #define QCOM_CLK_REF_EN_MASK BIT(0) struct qcom_clk_ref_provider { size_t num_refs; struct qcom_clk_ref refs[] __counted_by(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_set_bits(rclk->regmap, rclk->desc.offset, QCOM_CLK_REF_EN_MASK); if (ret) return ret; udelay(10); return 0; } static void qcom_clk_ref_disable(struct clk_hw *hw) { struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw); regmap_clear_bits(rclk->regmap, rclk->desc.offset, QCOM_CLK_REF_EN_MASK); 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 0; 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 * const *descs, size_t num_clk_refs) { const struct qcom_clk_ref_desc *desc; struct clk_init_data init_data = {}; 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]; /* Skip unpopulated indices; the array is indexed by clock ID. */ if (!desc) continue; if (WARN_ON(!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); } init_data.name = clk_ref->desc.name; init_data.parent_data = &qcom_clk_ref_parent_data; init_data.num_parents = 1; init_data.ops = &qcom_clk_ref_ops; clk_ref->hw.init = &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 * const *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, struct_size(provider, refs, num_clk_refs), GFP_KERNEL); if (!provider) 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);