162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/export.h> 762306a36Sopenharmony_ci#include <linux/module.h> 862306a36Sopenharmony_ci#include <linux/regmap.h> 962306a36Sopenharmony_ci#include <linux/platform_device.h> 1062306a36Sopenharmony_ci#include <linux/clk-provider.h> 1162306a36Sopenharmony_ci#include <linux/reset-controller.h> 1262306a36Sopenharmony_ci#include <linux/of.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include "common.h" 1562306a36Sopenharmony_ci#include "clk-rcg.h" 1662306a36Sopenharmony_ci#include "clk-regmap.h" 1762306a36Sopenharmony_ci#include "reset.h" 1862306a36Sopenharmony_ci#include "gdsc.h" 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistruct qcom_cc { 2162306a36Sopenharmony_ci struct qcom_reset_controller reset; 2262306a36Sopenharmony_ci struct clk_regmap **rclks; 2362306a36Sopenharmony_ci size_t num_rclks; 2462306a36Sopenharmony_ci}; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ciconst 2762306a36Sopenharmony_cistruct freq_tbl *qcom_find_freq(const struct freq_tbl *f, unsigned long rate) 2862306a36Sopenharmony_ci{ 2962306a36Sopenharmony_ci if (!f) 3062306a36Sopenharmony_ci return NULL; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci if (!f->freq) 3362306a36Sopenharmony_ci return f; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci for (; f->freq; f++) 3662306a36Sopenharmony_ci if (rate <= f->freq) 3762306a36Sopenharmony_ci return f; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci /* Default to our fastest rate */ 4062306a36Sopenharmony_ci return f - 1; 4162306a36Sopenharmony_ci} 4262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_find_freq); 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ciconst struct freq_tbl *qcom_find_freq_floor(const struct freq_tbl *f, 4562306a36Sopenharmony_ci unsigned long rate) 4662306a36Sopenharmony_ci{ 4762306a36Sopenharmony_ci const struct freq_tbl *best = NULL; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci for ( ; f->freq; f++) { 5062306a36Sopenharmony_ci if (rate >= f->freq) 5162306a36Sopenharmony_ci best = f; 5262306a36Sopenharmony_ci else 5362306a36Sopenharmony_ci break; 5462306a36Sopenharmony_ci } 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci return best; 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_find_freq_floor); 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ciint qcom_find_src_index(struct clk_hw *hw, const struct parent_map *map, u8 src) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci int i, num_parents = clk_hw_get_num_parents(hw); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci for (i = 0; i < num_parents; i++) 6562306a36Sopenharmony_ci if (src == map[i].src) 6662306a36Sopenharmony_ci return i; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci return -ENOENT; 6962306a36Sopenharmony_ci} 7062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_find_src_index); 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ciint qcom_find_cfg_index(struct clk_hw *hw, const struct parent_map *map, u8 cfg) 7362306a36Sopenharmony_ci{ 7462306a36Sopenharmony_ci int i, num_parents = clk_hw_get_num_parents(hw); 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci for (i = 0; i < num_parents; i++) 7762306a36Sopenharmony_ci if (cfg == map[i].cfg) 7862306a36Sopenharmony_ci return i; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci return -ENOENT; 8162306a36Sopenharmony_ci} 8262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_find_cfg_index); 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistruct regmap * 8562306a36Sopenharmony_ciqcom_cc_map(struct platform_device *pdev, const struct qcom_cc_desc *desc) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci void __iomem *base; 8862306a36Sopenharmony_ci struct device *dev = &pdev->dev; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci base = devm_platform_ioremap_resource(pdev, 0); 9162306a36Sopenharmony_ci if (IS_ERR(base)) 9262306a36Sopenharmony_ci return ERR_CAST(base); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci return devm_regmap_init_mmio(dev, base, desc->config); 9562306a36Sopenharmony_ci} 9662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_cc_map); 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_civoid 9962306a36Sopenharmony_ciqcom_pll_set_fsm_mode(struct regmap *map, u32 reg, u8 bias_count, u8 lock_count) 10062306a36Sopenharmony_ci{ 10162306a36Sopenharmony_ci u32 val; 10262306a36Sopenharmony_ci u32 mask; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci /* De-assert reset to FSM */ 10562306a36Sopenharmony_ci regmap_update_bits(map, reg, PLL_VOTE_FSM_RESET, 0); 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci /* Program bias count and lock count */ 10862306a36Sopenharmony_ci val = bias_count << PLL_BIAS_COUNT_SHIFT | 10962306a36Sopenharmony_ci lock_count << PLL_LOCK_COUNT_SHIFT; 11062306a36Sopenharmony_ci mask = PLL_BIAS_COUNT_MASK << PLL_BIAS_COUNT_SHIFT; 11162306a36Sopenharmony_ci mask |= PLL_LOCK_COUNT_MASK << PLL_LOCK_COUNT_SHIFT; 11262306a36Sopenharmony_ci regmap_update_bits(map, reg, mask, val); 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci /* Enable PLL FSM voting */ 11562306a36Sopenharmony_ci regmap_update_bits(map, reg, PLL_VOTE_FSM_ENA, PLL_VOTE_FSM_ENA); 11662306a36Sopenharmony_ci} 11762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_pll_set_fsm_mode); 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_cistatic void qcom_cc_gdsc_unregister(void *data) 12062306a36Sopenharmony_ci{ 12162306a36Sopenharmony_ci gdsc_unregister(data); 12262306a36Sopenharmony_ci} 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci/* 12562306a36Sopenharmony_ci * Backwards compatibility with old DTs. Register a pass-through factor 1/1 12662306a36Sopenharmony_ci * clock to translate 'path' clk into 'name' clk and register the 'path' 12762306a36Sopenharmony_ci * clk as a fixed rate clock if it isn't present. 12862306a36Sopenharmony_ci */ 12962306a36Sopenharmony_cistatic int _qcom_cc_register_board_clk(struct device *dev, const char *path, 13062306a36Sopenharmony_ci const char *name, unsigned long rate, 13162306a36Sopenharmony_ci bool add_factor) 13262306a36Sopenharmony_ci{ 13362306a36Sopenharmony_ci struct device_node *node = NULL; 13462306a36Sopenharmony_ci struct device_node *clocks_node; 13562306a36Sopenharmony_ci struct clk_fixed_factor *factor; 13662306a36Sopenharmony_ci struct clk_fixed_rate *fixed; 13762306a36Sopenharmony_ci struct clk_init_data init_data = { }; 13862306a36Sopenharmony_ci int ret; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci clocks_node = of_find_node_by_path("/clocks"); 14162306a36Sopenharmony_ci if (clocks_node) { 14262306a36Sopenharmony_ci node = of_get_child_by_name(clocks_node, path); 14362306a36Sopenharmony_ci of_node_put(clocks_node); 14462306a36Sopenharmony_ci } 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci if (!node) { 14762306a36Sopenharmony_ci fixed = devm_kzalloc(dev, sizeof(*fixed), GFP_KERNEL); 14862306a36Sopenharmony_ci if (!fixed) 14962306a36Sopenharmony_ci return -EINVAL; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci fixed->fixed_rate = rate; 15262306a36Sopenharmony_ci fixed->hw.init = &init_data; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci init_data.name = path; 15562306a36Sopenharmony_ci init_data.ops = &clk_fixed_rate_ops; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci ret = devm_clk_hw_register(dev, &fixed->hw); 15862306a36Sopenharmony_ci if (ret) 15962306a36Sopenharmony_ci return ret; 16062306a36Sopenharmony_ci } 16162306a36Sopenharmony_ci of_node_put(node); 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci if (add_factor) { 16462306a36Sopenharmony_ci factor = devm_kzalloc(dev, sizeof(*factor), GFP_KERNEL); 16562306a36Sopenharmony_ci if (!factor) 16662306a36Sopenharmony_ci return -EINVAL; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci factor->mult = factor->div = 1; 16962306a36Sopenharmony_ci factor->hw.init = &init_data; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci init_data.name = name; 17262306a36Sopenharmony_ci init_data.parent_names = &path; 17362306a36Sopenharmony_ci init_data.num_parents = 1; 17462306a36Sopenharmony_ci init_data.flags = 0; 17562306a36Sopenharmony_ci init_data.ops = &clk_fixed_factor_ops; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci ret = devm_clk_hw_register(dev, &factor->hw); 17862306a36Sopenharmony_ci if (ret) 17962306a36Sopenharmony_ci return ret; 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci return 0; 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ciint qcom_cc_register_board_clk(struct device *dev, const char *path, 18662306a36Sopenharmony_ci const char *name, unsigned long rate) 18762306a36Sopenharmony_ci{ 18862306a36Sopenharmony_ci bool add_factor = true; 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci /* 19162306a36Sopenharmony_ci * TODO: The RPM clock driver currently does not support the xo clock. 19262306a36Sopenharmony_ci * When xo is added to the RPM clock driver, we should change this 19362306a36Sopenharmony_ci * function to skip registration of xo factor clocks. 19462306a36Sopenharmony_ci */ 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci return _qcom_cc_register_board_clk(dev, path, name, rate, add_factor); 19762306a36Sopenharmony_ci} 19862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_cc_register_board_clk); 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ciint qcom_cc_register_sleep_clk(struct device *dev) 20162306a36Sopenharmony_ci{ 20262306a36Sopenharmony_ci return _qcom_cc_register_board_clk(dev, "sleep_clk", "sleep_clk_src", 20362306a36Sopenharmony_ci 32768, true); 20462306a36Sopenharmony_ci} 20562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_cc_register_sleep_clk); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci/* Drop 'protected-clocks' from the list of clocks to register */ 20862306a36Sopenharmony_cistatic void qcom_cc_drop_protected(struct device *dev, struct qcom_cc *cc) 20962306a36Sopenharmony_ci{ 21062306a36Sopenharmony_ci struct device_node *np = dev->of_node; 21162306a36Sopenharmony_ci struct property *prop; 21262306a36Sopenharmony_ci const __be32 *p; 21362306a36Sopenharmony_ci u32 i; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci of_property_for_each_u32(np, "protected-clocks", prop, p, i) { 21662306a36Sopenharmony_ci if (i >= cc->num_rclks) 21762306a36Sopenharmony_ci continue; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci cc->rclks[i] = NULL; 22062306a36Sopenharmony_ci } 22162306a36Sopenharmony_ci} 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_cistatic struct clk_hw *qcom_cc_clk_hw_get(struct of_phandle_args *clkspec, 22462306a36Sopenharmony_ci void *data) 22562306a36Sopenharmony_ci{ 22662306a36Sopenharmony_ci struct qcom_cc *cc = data; 22762306a36Sopenharmony_ci unsigned int idx = clkspec->args[0]; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci if (idx >= cc->num_rclks) { 23062306a36Sopenharmony_ci pr_err("%s: invalid index %u\n", __func__, idx); 23162306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 23262306a36Sopenharmony_ci } 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci return cc->rclks[idx] ? &cc->rclks[idx]->hw : NULL; 23562306a36Sopenharmony_ci} 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ciint qcom_cc_really_probe(struct platform_device *pdev, 23862306a36Sopenharmony_ci const struct qcom_cc_desc *desc, struct regmap *regmap) 23962306a36Sopenharmony_ci{ 24062306a36Sopenharmony_ci int i, ret; 24162306a36Sopenharmony_ci struct device *dev = &pdev->dev; 24262306a36Sopenharmony_ci struct qcom_reset_controller *reset; 24362306a36Sopenharmony_ci struct qcom_cc *cc; 24462306a36Sopenharmony_ci struct gdsc_desc *scd; 24562306a36Sopenharmony_ci size_t num_clks = desc->num_clks; 24662306a36Sopenharmony_ci struct clk_regmap **rclks = desc->clks; 24762306a36Sopenharmony_ci size_t num_clk_hws = desc->num_clk_hws; 24862306a36Sopenharmony_ci struct clk_hw **clk_hws = desc->clk_hws; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci cc = devm_kzalloc(dev, sizeof(*cc), GFP_KERNEL); 25162306a36Sopenharmony_ci if (!cc) 25262306a36Sopenharmony_ci return -ENOMEM; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci reset = &cc->reset; 25562306a36Sopenharmony_ci reset->rcdev.of_node = dev->of_node; 25662306a36Sopenharmony_ci reset->rcdev.ops = &qcom_reset_ops; 25762306a36Sopenharmony_ci reset->rcdev.owner = dev->driver->owner; 25862306a36Sopenharmony_ci reset->rcdev.nr_resets = desc->num_resets; 25962306a36Sopenharmony_ci reset->regmap = regmap; 26062306a36Sopenharmony_ci reset->reset_map = desc->resets; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci ret = devm_reset_controller_register(dev, &reset->rcdev); 26362306a36Sopenharmony_ci if (ret) 26462306a36Sopenharmony_ci return ret; 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci if (desc->gdscs && desc->num_gdscs) { 26762306a36Sopenharmony_ci scd = devm_kzalloc(dev, sizeof(*scd), GFP_KERNEL); 26862306a36Sopenharmony_ci if (!scd) 26962306a36Sopenharmony_ci return -ENOMEM; 27062306a36Sopenharmony_ci scd->dev = dev; 27162306a36Sopenharmony_ci scd->scs = desc->gdscs; 27262306a36Sopenharmony_ci scd->num = desc->num_gdscs; 27362306a36Sopenharmony_ci ret = gdsc_register(scd, &reset->rcdev, regmap); 27462306a36Sopenharmony_ci if (ret) 27562306a36Sopenharmony_ci return ret; 27662306a36Sopenharmony_ci ret = devm_add_action_or_reset(dev, qcom_cc_gdsc_unregister, 27762306a36Sopenharmony_ci scd); 27862306a36Sopenharmony_ci if (ret) 27962306a36Sopenharmony_ci return ret; 28062306a36Sopenharmony_ci } 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci cc->rclks = rclks; 28362306a36Sopenharmony_ci cc->num_rclks = num_clks; 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci qcom_cc_drop_protected(dev, cc); 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci for (i = 0; i < num_clk_hws; i++) { 28862306a36Sopenharmony_ci ret = devm_clk_hw_register(dev, clk_hws[i]); 28962306a36Sopenharmony_ci if (ret) 29062306a36Sopenharmony_ci return ret; 29162306a36Sopenharmony_ci } 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci for (i = 0; i < num_clks; i++) { 29462306a36Sopenharmony_ci if (!rclks[i]) 29562306a36Sopenharmony_ci continue; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci ret = devm_clk_register_regmap(dev, rclks[i]); 29862306a36Sopenharmony_ci if (ret) 29962306a36Sopenharmony_ci return ret; 30062306a36Sopenharmony_ci } 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci ret = devm_of_clk_add_hw_provider(dev, qcom_cc_clk_hw_get, cc); 30362306a36Sopenharmony_ci if (ret) 30462306a36Sopenharmony_ci return ret; 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci return 0; 30762306a36Sopenharmony_ci} 30862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_cc_really_probe); 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ciint qcom_cc_probe(struct platform_device *pdev, const struct qcom_cc_desc *desc) 31162306a36Sopenharmony_ci{ 31262306a36Sopenharmony_ci struct regmap *regmap; 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci regmap = qcom_cc_map(pdev, desc); 31562306a36Sopenharmony_ci if (IS_ERR(regmap)) 31662306a36Sopenharmony_ci return PTR_ERR(regmap); 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci return qcom_cc_really_probe(pdev, desc, regmap); 31962306a36Sopenharmony_ci} 32062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_cc_probe); 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ciint qcom_cc_probe_by_index(struct platform_device *pdev, int index, 32362306a36Sopenharmony_ci const struct qcom_cc_desc *desc) 32462306a36Sopenharmony_ci{ 32562306a36Sopenharmony_ci struct regmap *regmap; 32662306a36Sopenharmony_ci void __iomem *base; 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci base = devm_platform_ioremap_resource(pdev, index); 32962306a36Sopenharmony_ci if (IS_ERR(base)) 33062306a36Sopenharmony_ci return -ENOMEM; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci regmap = devm_regmap_init_mmio(&pdev->dev, base, desc->config); 33362306a36Sopenharmony_ci if (IS_ERR(regmap)) 33462306a36Sopenharmony_ci return PTR_ERR(regmap); 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci return qcom_cc_really_probe(pdev, desc, regmap); 33762306a36Sopenharmony_ci} 33862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_cc_probe_by_index); 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 341