| 1 | |
| 2 | |
| 3 | * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
|
| 4 |
|
| 5 |
|
| 6 | #include <linux/export.h>
|
| 7 | #include <linux/module.h>
|
| 8 | #include <linux/regmap.h>
|
| 9 | #include <linux/platform_device.h>
|
| 10 | #include <linux/clk-provider.h>
|
| 11 | #include <linux/interconnect-clk.h>
|
| 12 | #include <linux/pm_runtime.h>
|
| 13 | #include <linux/reset-controller.h>
|
| 14 | #include <linux/of.h>
|
| 15 |
|
| 16 | #include "common.h"
|
| 17 | #include "clk-alpha-pll.h"
|
| 18 | #include "clk-branch.h"
|
| 19 | #include "clk-rcg.h"
|
| 20 | #include "clk-regmap.h"
|
| 21 | #include "reset.h"
|
| 22 | #include "gdsc.h"
|
| 23 |
|
| 24 | struct qcom_cc {
|
| 25 | struct qcom_reset_controller reset;
|
| 26 | struct clk_regmap **rclks;
|
| 27 | size_t num_rclks;
|
| 28 | struct dev_pm_domain_list *pd_list;
|
| 29 | };
|
| 30 |
|
| 31 | const
|
| 32 | struct freq_tbl *qcom_find_freq(const struct freq_tbl *f, unsigned long rate)
|
| 33 | {
|
| 34 | if (!f)
|
| 35 | return NULL;
|
| 36 |
|
| 37 | if (!f->freq)
|
| 38 | return f;
|
| 39 |
|
| 40 | for (; f->freq; f++)
|
| 41 | if (rate <= f->freq)
|
| 42 | return f;
|
| 43 |
|
| 44 |
|
| 45 | return f - 1;
|
| 46 | }
|
| 47 | EXPORT_SYMBOL_GPL(qcom_find_freq);
|
| 48 |
|
| 49 | const struct freq_multi_tbl *qcom_find_freq_multi(const struct freq_multi_tbl *f,
|
| 50 | unsigned long rate)
|
| 51 | {
|
| 52 | if (!f)
|
| 53 | return NULL;
|
| 54 |
|
| 55 | if (!f->freq)
|
| 56 | return f;
|
| 57 |
|
| 58 | for (; f->freq; f++)
|
| 59 | if (rate <= f->freq)
|
| 60 | return f;
|
| 61 |
|
| 62 |
|
| 63 | return f - 1;
|
| 64 | }
|
| 65 | EXPORT_SYMBOL_GPL(qcom_find_freq_multi);
|
| 66 |
|
| 67 | const struct freq_tbl *qcom_find_freq_floor(const struct freq_tbl *f,
|
| 68 | unsigned long rate)
|
| 69 | {
|
| 70 | const struct freq_tbl *best = NULL;
|
| 71 |
|
| 72 | for ( ; f->freq; f++) {
|
| 73 | if (rate >= f->freq)
|
| 74 | best = f;
|
| 75 | else
|
| 76 | break;
|
| 77 | }
|
| 78 |
|
| 79 | return best;
|
| 80 | }
|
| 81 | EXPORT_SYMBOL_GPL(qcom_find_freq_floor);
|
| 82 |
|
| 83 | int qcom_find_src_index(struct clk_hw *hw, const struct parent_map *map, u8 src)
|
| 84 | {
|
| 85 | int i, num_parents = clk_hw_get_num_parents(hw);
|
| 86 |
|
| 87 | for (i = 0; i < num_parents; i++)
|
| 88 | if (src == map[i].src)
|
| 89 | return i;
|
| 90 |
|
| 91 | return -ENOENT;
|
| 92 | }
|
| 93 | EXPORT_SYMBOL_GPL(qcom_find_src_index);
|
| 94 |
|
| 95 | int qcom_find_cfg_index(struct clk_hw *hw, const struct parent_map *map, u8 cfg)
|
| 96 | {
|
| 97 | int i, num_parents = clk_hw_get_num_parents(hw);
|
| 98 |
|
| 99 | for (i = 0; i < num_parents; i++)
|
| 100 | if (cfg == map[i].cfg)
|
| 101 | return i;
|
| 102 |
|
| 103 | return -ENOENT;
|
| 104 | }
|
| 105 | EXPORT_SYMBOL_GPL(qcom_find_cfg_index);
|
| 106 |
|
| 107 | struct regmap *
|
| 108 | qcom_cc_map(struct platform_device *pdev, const struct qcom_cc_desc *desc)
|
| 109 | {
|
| 110 | void __iomem *base;
|
| 111 | struct device *dev = &pdev->dev;
|
| 112 |
|
| 113 | base = devm_platform_ioremap_resource(pdev, 0);
|
| 114 | if (IS_ERR(base))
|
| 115 | return ERR_CAST(base);
|
| 116 |
|
| 117 | return devm_regmap_init_mmio(dev, base, desc->config);
|
| 118 | }
|
| 119 | EXPORT_SYMBOL_GPL(qcom_cc_map);
|
| 120 |
|
| 121 | void
|
| 122 | qcom_pll_set_fsm_mode(struct regmap *map, u32 reg, u8 bias_count, u8 lock_count)
|
| 123 | {
|
| 124 | u32 val;
|
| 125 | u32 mask;
|
| 126 |
|
| 127 |
|
| 128 | regmap_update_bits(map, reg, PLL_VOTE_FSM_RESET, 0);
|
| 129 |
|
| 130 |
|
| 131 | val = bias_count << PLL_BIAS_COUNT_SHIFT |
|
| 132 | lock_count << PLL_LOCK_COUNT_SHIFT;
|
| 133 | mask = PLL_BIAS_COUNT_MASK << PLL_BIAS_COUNT_SHIFT;
|
| 134 | mask |= PLL_LOCK_COUNT_MASK << PLL_LOCK_COUNT_SHIFT;
|
| 135 | regmap_update_bits(map, reg, mask, val);
|
| 136 |
|
| 137 |
|
| 138 | regmap_update_bits(map, reg, PLL_VOTE_FSM_ENA, PLL_VOTE_FSM_ENA);
|
| 139 | }
|
| 140 | EXPORT_SYMBOL_GPL(qcom_pll_set_fsm_mode);
|
| 141 |
|
| 142 | static void qcom_cc_gdsc_unregister(void *data)
|
| 143 | {
|
| 144 | gdsc_unregister(data);
|
| 145 | }
|
| 146 |
|
| 147 | |
| 148 | * Backwards compatibility with old DTs. Register a pass-through factor 1/1
|
| 149 | * clock to translate 'path' clk into 'name' clk and register the 'path'
|
| 150 | * clk as a fixed rate clock if it isn't present.
|
| 151 |
|
| 152 | static int _qcom_cc_register_board_clk(struct device *dev, const char *path,
|
| 153 | const char *name, unsigned long rate,
|
| 154 | bool add_factor)
|
| 155 | {
|
| 156 | struct device_node *node = NULL;
|
| 157 | struct device_node *clocks_node;
|
| 158 | struct clk_fixed_factor *factor;
|
| 159 | struct clk_fixed_rate *fixed;
|
| 160 | struct clk_init_data init_data = { };
|
| 161 | int ret;
|
| 162 |
|
| 163 | clocks_node = of_find_node_by_path("/clocks");
|
| 164 | if (clocks_node) {
|
| 165 | node = of_get_child_by_name(clocks_node, path);
|
| 166 | of_node_put(clocks_node);
|
| 167 | }
|
| 168 |
|
| 169 | if (!node) {
|
| 170 | fixed = devm_kzalloc(dev, sizeof(*fixed), GFP_KERNEL);
|
| 171 | if (!fixed)
|
| 172 | return -ENOMEM;
|
| 173 |
|
| 174 | fixed->fixed_rate = rate;
|
| 175 | fixed->hw.init = &init_data;
|
| 176 |
|
| 177 | init_data.name = path;
|
| 178 | init_data.ops = &clk_fixed_rate_ops;
|
| 179 |
|
| 180 | ret = devm_clk_hw_register(dev, &fixed->hw);
|
| 181 | if (ret)
|
| 182 | return ret;
|
| 183 | }
|
| 184 | of_node_put(node);
|
| 185 |
|
| 186 | if (add_factor) {
|
| 187 | factor = devm_kzalloc(dev, sizeof(*factor), GFP_KERNEL);
|
| 188 | if (!factor)
|
| 189 | return -ENOMEM;
|
| 190 |
|
| 191 | factor->mult = factor->div = 1;
|
| 192 | factor->hw.init = &init_data;
|
| 193 |
|
| 194 | init_data.name = name;
|
| 195 | init_data.parent_names = &path;
|
| 196 | init_data.num_parents = 1;
|
| 197 | init_data.flags = 0;
|
| 198 | init_data.ops = &clk_fixed_factor_ops;
|
| 199 |
|
| 200 | ret = devm_clk_hw_register(dev, &factor->hw);
|
| 201 | if (ret)
|
| 202 | return ret;
|
| 203 | }
|
| 204 |
|
| 205 | return 0;
|
| 206 | }
|
| 207 |
|
| 208 | int qcom_cc_register_board_clk(struct device *dev, const char *path,
|
| 209 | const char *name, unsigned long rate)
|
| 210 | {
|
| 211 | bool add_factor = true;
|
| 212 |
|
| 213 | |
| 214 | * TODO: The RPM clock driver currently does not support the xo clock.
|
| 215 | * When xo is added to the RPM clock driver, we should change this
|
| 216 | * function to skip registration of xo factor clocks.
|
| 217 |
|
| 218 |
|
| 219 | return _qcom_cc_register_board_clk(dev, path, name, rate, add_factor);
|
| 220 | }
|
| 221 | EXPORT_SYMBOL_GPL(qcom_cc_register_board_clk);
|
| 222 |
|
| 223 | int qcom_cc_register_sleep_clk(struct device *dev)
|
| 224 | {
|
| 225 | return _qcom_cc_register_board_clk(dev, "sleep_clk", "sleep_clk_src",
|
| 226 | 32768, true);
|
| 227 | }
|
| 228 | EXPORT_SYMBOL_GPL(qcom_cc_register_sleep_clk);
|
| 229 |
|
| 230 |
|
| 231 | static void qcom_cc_drop_protected(struct device *dev, struct qcom_cc *cc)
|
| 232 | {
|
| 233 | struct device_node *np = dev->of_node;
|
| 234 | u32 i;
|
| 235 |
|
| 236 | of_property_for_each_u32(np, "protected-clocks", i) {
|
| 237 | if (i >= cc->num_rclks)
|
| 238 | continue;
|
| 239 |
|
| 240 | cc->rclks[i] = NULL;
|
| 241 | }
|
| 242 | }
|
| 243 |
|
| 244 | static struct clk_hw *qcom_cc_clk_hw_get(struct of_phandle_args *clkspec,
|
| 245 | void *data)
|
| 246 | {
|
| 247 | struct qcom_cc *cc = data;
|
| 248 | unsigned int idx = clkspec->args[0];
|
| 249 |
|
| 250 | if (idx >= cc->num_rclks) {
|
| 251 | pr_err("%s: invalid index %u\n", __func__, idx);
|
| 252 | return ERR_PTR(-EINVAL);
|
| 253 | }
|
| 254 |
|
| 255 | return cc->rclks[idx] ? &cc->rclks[idx]->hw : NULL;
|
| 256 | }
|
| 257 |
|
| 258 | static int qcom_cc_icc_register(struct device *dev,
|
| 259 | const struct qcom_cc_desc *desc)
|
| 260 | {
|
| 261 | struct icc_clk_data *icd;
|
| 262 | struct clk_hw *hws;
|
| 263 | int i;
|
| 264 |
|
| 265 | if (!IS_ENABLED(CONFIG_INTERCONNECT_CLK))
|
| 266 | return 0;
|
| 267 |
|
| 268 | if (!desc->icc_hws)
|
| 269 | return 0;
|
| 270 |
|
| 271 | icd = devm_kcalloc(dev, desc->num_icc_hws, sizeof(*icd), GFP_KERNEL);
|
| 272 | if (!icd)
|
| 273 | return -ENOMEM;
|
| 274 |
|
| 275 | for (i = 0; i < desc->num_icc_hws; i++) {
|
| 276 | icd[i].master_id = desc->icc_hws[i].master_id;
|
| 277 | icd[i].slave_id = desc->icc_hws[i].slave_id;
|
| 278 | hws = &desc->clks[desc->icc_hws[i].clk_id]->hw;
|
| 279 | icd[i].clk = devm_clk_hw_get_clk(dev, hws, "icc");
|
| 280 | if (IS_ERR(icd[i].clk))
|
| 281 | return dev_err_probe(dev, PTR_ERR(icd[i].clk),
|
| 282 | "(%d) clock entry is null\n", i);
|
| 283 | icd[i].name = clk_hw_get_name(hws);
|
| 284 | }
|
| 285 |
|
| 286 | return devm_icc_clk_register(dev, desc->icc_first_node_id,
|
| 287 | desc->num_icc_hws, icd);
|
| 288 | }
|
| 289 |
|
| 290 | static int qcom_cc_clk_pll_configure(const struct qcom_cc_driver_data *data,
|
| 291 | struct regmap *regmap)
|
| 292 | {
|
| 293 | const struct clk_init_data *init;
|
| 294 | struct clk_alpha_pll *pll;
|
| 295 | int i;
|
| 296 |
|
| 297 | for (i = 0; i < data->num_alpha_plls; i++) {
|
| 298 | pll = data->alpha_plls[i];
|
| 299 | init = pll->clkr.hw.init;
|
| 300 |
|
| 301 | if (!pll->config || !pll->regs) {
|
| 302 | pr_err("%s: missing pll config or regs\n", init->name);
|
| 303 | return -EINVAL;
|
| 304 | }
|
| 305 |
|
| 306 | qcom_clk_alpha_pll_configure(pll, regmap);
|
| 307 | }
|
| 308 |
|
| 309 | return 0;
|
| 310 | }
|
| 311 |
|
| 312 | static void qcom_cc_clk_regs_configure(struct device *dev, const struct qcom_cc_driver_data *data,
|
| 313 | struct regmap *regmap)
|
| 314 | {
|
| 315 | int i;
|
| 316 |
|
| 317 | for (i = 0; i < data->num_clk_cbcrs; i++)
|
| 318 | qcom_branch_set_clk_en(regmap, data->clk_cbcrs[i]);
|
| 319 |
|
| 320 | if (data->clk_regs_configure)
|
| 321 | data->clk_regs_configure(dev, regmap);
|
| 322 | }
|
| 323 |
|
| 324 | int qcom_cc_really_probe(struct device *dev,
|
| 325 | const struct qcom_cc_desc *desc, struct regmap *regmap)
|
| 326 | {
|
| 327 | int i, ret;
|
| 328 | struct qcom_reset_controller *reset;
|
| 329 | struct qcom_cc *cc;
|
| 330 | struct gdsc_desc *scd;
|
| 331 | size_t num_clks = desc->num_clks;
|
| 332 | struct clk_regmap **rclks = desc->clks;
|
| 333 | size_t num_clk_hws = desc->num_clk_hws;
|
| 334 | struct clk_hw **clk_hws = desc->clk_hws;
|
| 335 |
|
| 336 | cc = devm_kzalloc(dev, sizeof(*cc), GFP_KERNEL);
|
| 337 | if (!cc)
|
| 338 | return -ENOMEM;
|
| 339 |
|
| 340 | ret = devm_pm_domain_attach_list(dev, NULL, &cc->pd_list);
|
| 341 | if (ret < 0 && ret != -EEXIST)
|
| 342 | return ret;
|
| 343 |
|
| 344 | if (desc->use_rpm) {
|
| 345 | ret = devm_pm_runtime_enable(dev);
|
| 346 | if (ret)
|
| 347 | return ret;
|
| 348 |
|
| 349 | ret = pm_runtime_resume_and_get(dev);
|
| 350 | if (ret)
|
| 351 | return ret;
|
| 352 | }
|
| 353 |
|
| 354 | if (desc->driver_data) {
|
| 355 | ret = qcom_cc_clk_pll_configure(desc->driver_data, regmap);
|
| 356 | if (ret)
|
| 357 | goto put_rpm;
|
| 358 |
|
| 359 | qcom_cc_clk_regs_configure(dev, desc->driver_data, regmap);
|
| 360 | }
|
| 361 |
|
| 362 | reset = &cc->reset;
|
| 363 | reset->rcdev.of_node = dev->of_node;
|
| 364 | reset->rcdev.ops = &qcom_reset_ops;
|
| 365 | reset->rcdev.owner = dev->driver->owner;
|
| 366 | reset->rcdev.nr_resets = desc->num_resets;
|
| 367 | reset->regmap = regmap;
|
| 368 | reset->reset_map = desc->resets;
|
| 369 |
|
| 370 | ret = devm_reset_controller_register(dev, &reset->rcdev);
|
| 371 | if (ret)
|
| 372 | goto put_rpm;
|
| 373 |
|
| 374 | if (desc->gdscs && desc->num_gdscs) {
|
| 375 | scd = devm_kzalloc(dev, sizeof(*scd), GFP_KERNEL);
|
| 376 | if (!scd) {
|
| 377 | ret = -ENOMEM;
|
| 378 | goto put_rpm;
|
| 379 | }
|
| 380 | scd->dev = dev;
|
| 381 | scd->scs = desc->gdscs;
|
| 382 | scd->num = desc->num_gdscs;
|
| 383 | scd->pd_list = cc->pd_list;
|
| 384 | ret = gdsc_register(scd, &reset->rcdev, regmap);
|
| 385 | if (ret)
|
| 386 | goto put_rpm;
|
| 387 | ret = devm_add_action_or_reset(dev, qcom_cc_gdsc_unregister,
|
| 388 | scd);
|
| 389 | if (ret)
|
| 390 | goto put_rpm;
|
| 391 | }
|
| 392 |
|
| 393 | if (desc->driver_data &&
|
| 394 | desc->driver_data->dfs_rcgs &&
|
| 395 | desc->driver_data->num_dfs_rcgs) {
|
| 396 | ret = qcom_cc_register_rcg_dfs(regmap,
|
| 397 | desc->driver_data->dfs_rcgs,
|
| 398 | desc->driver_data->num_dfs_rcgs);
|
| 399 | if (ret)
|
| 400 | goto put_rpm;
|
| 401 | }
|
| 402 |
|
| 403 | cc->rclks = rclks;
|
| 404 | cc->num_rclks = num_clks;
|
| 405 |
|
| 406 | qcom_cc_drop_protected(dev, cc);
|
| 407 |
|
| 408 | for (i = 0; i < num_clk_hws; i++) {
|
| 409 | ret = devm_clk_hw_register(dev, clk_hws[i]);
|
| 410 | if (ret)
|
| 411 | goto put_rpm;
|
| 412 | }
|
| 413 |
|
| 414 | for (i = 0; i < num_clks; i++) {
|
| 415 | if (!rclks[i])
|
| 416 | continue;
|
| 417 |
|
| 418 | ret = devm_clk_register_regmap(dev, rclks[i]);
|
| 419 | if (ret)
|
| 420 | goto put_rpm;
|
| 421 | }
|
| 422 |
|
| 423 | ret = devm_of_clk_add_hw_provider(dev, qcom_cc_clk_hw_get, cc);
|
| 424 | if (ret)
|
| 425 | goto put_rpm;
|
| 426 |
|
| 427 | ret = qcom_cc_icc_register(dev, desc);
|
| 428 |
|
| 429 | put_rpm:
|
| 430 | if (desc->use_rpm)
|
| 431 | pm_runtime_put_sync(dev);
|
| 432 |
|
| 433 | return ret;
|
| 434 | }
|
| 435 | EXPORT_SYMBOL_GPL(qcom_cc_really_probe);
|
| 436 |
|
| 437 | int qcom_cc_probe(struct platform_device *pdev, const struct qcom_cc_desc *desc)
|
| 438 | {
|
| 439 | struct regmap *regmap;
|
| 440 |
|
| 441 | regmap = qcom_cc_map(pdev, desc);
|
| 442 | if (IS_ERR(regmap))
|
| 443 | return PTR_ERR(regmap);
|
| 444 |
|
| 445 | return qcom_cc_really_probe(&pdev->dev, desc, regmap);
|
| 446 | }
|
| 447 | EXPORT_SYMBOL_GPL(qcom_cc_probe);
|
| 448 |
|
| 449 | int qcom_cc_probe_by_index(struct platform_device *pdev, int index,
|
| 450 | const struct qcom_cc_desc *desc)
|
| 451 | {
|
| 452 | struct regmap *regmap;
|
| 453 | void __iomem *base;
|
| 454 |
|
| 455 | base = devm_platform_ioremap_resource(pdev, index);
|
| 456 | if (IS_ERR(base))
|
| 457 | return PTR_ERR(base);
|
| 458 |
|
| 459 | regmap = devm_regmap_init_mmio(&pdev->dev, base, desc->config);
|
| 460 | if (IS_ERR(regmap))
|
| 461 | return PTR_ERR(regmap);
|
| 462 |
|
| 463 | return qcom_cc_really_probe(&pdev->dev, desc, regmap);
|
| 464 | }
|
| 465 | EXPORT_SYMBOL_GPL(qcom_cc_probe_by_index);
|
| 466 |
|
| 467 | MODULE_LICENSE("GPL v2");
|
| 468 | MODULE_DESCRIPTION("QTI Common Clock module");
|