1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries. 4 */ 5 6 #include <linux/clk-provider.h> 7 #include <linux/clk/qcom.h> 8 #include <linux/delay.h> 9 #include <linux/err.h> 10 #include <linux/export.h> 11 #include <linux/platform_device.h> 12 #include <linux/regmap.h> 13 #include <linux/regulator/consumer.h> 14 #include <linux/slab.h> 15 16 #define QCOM_CLK_REF_EN_MASK BIT(0) 17 18 struct qcom_clk_ref_provider { 19 size_t num_refs; 20 struct qcom_clk_ref refs[] __counted_by(num_refs); 21 }; 22 23 static inline struct qcom_clk_ref *to_qcom_clk_ref(struct clk_hw *hw) 24 { 25 return container_of(hw, struct qcom_clk_ref, hw); 26 } 27 28 static const struct clk_parent_data qcom_clk_ref_parent_data = { 29 .index = 0, 30 }; 31 32 static int qcom_clk_ref_prepare(struct clk_hw *hw) 33 { 34 struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw); 35 int ret; 36 37 if (!rclk->desc.num_regulators) 38 return 0; 39 40 ret = regulator_bulk_enable(rclk->desc.num_regulators, rclk->regulators); 41 if (ret) 42 pr_err("Failed to enable regulators for %s: %d\n", 43 clk_hw_get_name(hw), ret); 44 45 return ret; 46 } 47 48 static void qcom_clk_ref_unprepare(struct clk_hw *hw) 49 { 50 struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw); 51 52 if (rclk->desc.num_regulators) 53 regulator_bulk_disable(rclk->desc.num_regulators, rclk->regulators); 54 } 55 56 static int qcom_clk_ref_enable(struct clk_hw *hw) 57 { 58 struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw); 59 int ret; 60 61 ret = regmap_set_bits(rclk->regmap, rclk->desc.offset, QCOM_CLK_REF_EN_MASK); 62 if (ret) 63 return ret; 64 65 udelay(10); 66 67 return 0; 68 } 69 70 static void qcom_clk_ref_disable(struct clk_hw *hw) 71 { 72 struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw); 73 74 regmap_clear_bits(rclk->regmap, rclk->desc.offset, QCOM_CLK_REF_EN_MASK); 75 udelay(10); 76 } 77 78 static int qcom_clk_ref_is_enabled(struct clk_hw *hw) 79 { 80 struct qcom_clk_ref *rclk = to_qcom_clk_ref(hw); 81 u32 val; 82 int ret; 83 84 ret = regmap_read(rclk->regmap, rclk->desc.offset, &val); 85 if (ret) 86 return 0; 87 88 return !!(val & QCOM_CLK_REF_EN_MASK); 89 } 90 91 static const struct clk_ops qcom_clk_ref_ops = { 92 .prepare = qcom_clk_ref_prepare, 93 .unprepare = qcom_clk_ref_unprepare, 94 .enable = qcom_clk_ref_enable, 95 .disable = qcom_clk_ref_disable, 96 .is_enabled = qcom_clk_ref_is_enabled, 97 }; 98 99 static int qcom_clk_ref_register(struct device *dev, struct regmap *regmap, 100 struct qcom_clk_ref *clk_refs, 101 const struct qcom_clk_ref_desc * const *descs, 102 size_t num_clk_refs) 103 { 104 const struct qcom_clk_ref_desc *desc; 105 struct clk_init_data init_data = {}; 106 struct qcom_clk_ref *clk_ref; 107 size_t clk_idx; 108 unsigned int i; 109 int ret; 110 111 for (clk_idx = 0; clk_idx < num_clk_refs; clk_idx++) { 112 clk_ref = &clk_refs[clk_idx]; 113 desc = descs[clk_idx]; 114 115 /* Skip unpopulated indices; the array is indexed by clock ID. */ 116 if (!desc) 117 continue; 118 119 if (WARN_ON(!desc->name)) 120 continue; 121 122 clk_ref->regmap = regmap; 123 clk_ref->desc = *desc; 124 125 if (clk_ref->desc.num_regulators) { 126 clk_ref->regulators = devm_kcalloc(dev, clk_ref->desc.num_regulators, 127 sizeof(*clk_ref->regulators), 128 GFP_KERNEL); 129 if (!clk_ref->regulators) 130 return -ENOMEM; 131 132 for (i = 0; i < clk_ref->desc.num_regulators; i++) 133 clk_ref->regulators[i].supply = 134 clk_ref->desc.regulator_names[i]; 135 136 ret = devm_regulator_bulk_get(dev, clk_ref->desc.num_regulators, 137 clk_ref->regulators); 138 if (ret) 139 return dev_err_probe(dev, ret, 140 "Failed to get regulators for %s\n", 141 clk_ref->desc.name); 142 } 143 144 init_data.name = clk_ref->desc.name; 145 init_data.parent_data = &qcom_clk_ref_parent_data; 146 init_data.num_parents = 1; 147 init_data.ops = &qcom_clk_ref_ops; 148 clk_ref->hw.init = &init_data; 149 150 ret = devm_clk_hw_register(dev, &clk_ref->hw); 151 if (ret) 152 return ret; 153 } 154 155 return 0; 156 } 157 158 static struct clk_hw *qcom_clk_ref_provider_get(struct of_phandle_args *clkspec, void *data) 159 { 160 struct qcom_clk_ref_provider *provider = data; 161 unsigned int idx = clkspec->args[0]; 162 163 if (idx >= provider->num_refs) 164 return ERR_PTR(-EINVAL); 165 166 if (!provider->refs[idx].regmap) 167 return ERR_PTR(-ENOENT); 168 169 return &provider->refs[idx].hw; 170 } 171 172 int qcom_clk_ref_probe(struct platform_device *pdev, 173 const struct regmap_config *config, 174 const struct qcom_clk_ref_desc * const *descs, 175 size_t num_clk_refs) 176 { 177 struct qcom_clk_ref_provider *provider; 178 struct device *dev = &pdev->dev; 179 struct regmap *regmap; 180 void __iomem *base; 181 int ret; 182 183 base = devm_platform_ioremap_resource(pdev, 0); 184 if (IS_ERR(base)) 185 return PTR_ERR(base); 186 187 regmap = devm_regmap_init_mmio(dev, base, config); 188 if (IS_ERR(regmap)) 189 return PTR_ERR(regmap); 190 191 provider = devm_kzalloc(dev, struct_size(provider, refs, num_clk_refs), 192 GFP_KERNEL); 193 if (!provider) 194 return -ENOMEM; 195 196 provider->num_refs = num_clk_refs; 197 198 ret = qcom_clk_ref_register(dev, regmap, provider->refs, descs, 199 provider->num_refs); 200 if (ret) 201 return ret; 202 203 return devm_of_clk_add_hw_provider(dev, qcom_clk_ref_provider_get, provider); 204 } 205 EXPORT_SYMBOL_GPL(qcom_clk_ref_probe); 206